diff options
Diffstat (limited to 'src/qmldom')
55 files changed, 11654 insertions, 6185 deletions
diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt index 3edb917836..f0fffea605 100644 --- a/src/qmldom/CMakeLists.txt +++ b/src/qmldom/CMakeLists.txt @@ -14,6 +14,7 @@ qt_internal_add_module(QmlDomPrivate SOURCES qqmldom_fwd_p.h qqmldom_global.h + qqmldom_utils_p.h qqmldom_utils.cpp qqmldomastcreator.cpp qqmldomastcreator_p.h qqmldomastdumper.cpp qqmldomastdumper_p.h qqmldomattachedinfo.cpp qqmldomattachedinfo_p.h @@ -39,11 +40,14 @@ qt_internal_add_module(QmlDomPrivate 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::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 25cba4f8dd..9b8603b33e 100644 --- a/src/qmldom/qqmldom_fwd_p.h +++ b/src/qmldom/qqmldom_fwd_p.h @@ -30,6 +30,7 @@ class Comment; class CommentedElement; class ConstantData; class DomBase; +enum DomCreationOption : char; class DomEnvironment; class DomItem; class DomTop; @@ -41,6 +42,7 @@ class ExternalItemInfoBase; class ExternalItemPairBase; class ExternalOwningItem; class FileLocations; +enum FileLocationRegion : int; class FileWriter; class GlobalComponent; class GlobalScope; @@ -65,7 +67,7 @@ class Path; class Pragma; class PropertyDefinition; class PropertyInfo; -class QmlDomAstCreator; +class QQmlDomAstCreator; class QmlComponent; class QmlDirectory; class QmldirFile; @@ -80,6 +82,22 @@ class Source; class TestDomItem; class Version; +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 QT_END_NAMESPACE 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 index 40665845fc..b070b0ea6d 100644 --- a/src/qmldom/qqmldomastcreator.cpp +++ b/src/qmldom/qqmldomastcreator.cpp @@ -1,12 +1,21 @@ // 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> @@ -14,9 +23,31 @@ #include <QtCore/QLoggingCategory> #include <memory> +#include <optional> +#include <type_traits> #include <variant> - -static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg); +#include <vector> + +Q_STATIC_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 { @@ -46,7 +77,7 @@ V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx) return &(*it); } -static ErrorGroups myParseErrors() +static ErrorGroups astParseErrors() { static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"), NewErrorGroup("Parsing") } }; @@ -88,879 +119,2981 @@ SourceLocation combineLocations(Node *n) return combineLocations(n->firstSourceLocation(), n->lastSourceLocation()); } -class DomValue +static ScriptElementVariant wrapIntoFieldMemberExpression(const ScriptElementVariant &left, + const SourceLocation &dotToken, + const ScriptElementVariant &right) { -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; + 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); }; -class StackEl +/*! + \internal + Creates a FieldMemberExpression if the qualified id has dots. +*/ +static ScriptElementVariant +fieldMemberExpressionForQualifiedId(const AST::UiQualifiedId *qualifiedId) { -public: - Path path; - DomValue item; - FileLocations::Tree fileLocations; -}; + 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; +} -class QmlDomAstCreator final : public AST::Visitor +QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx) { - Q_DECLARE_TR_FUNCTIONS(QmlDomAstCreator) + 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(); +} - static constexpr const auto className = "QmlDomAstCreator"; +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]; +} - MutableDomItem qmlFile; - std::shared_ptr<QmlFile> qmlFilePtr; - QVector<StackEl> nodeStack; - QVector<int> arrayBindingLevels; - FileLocations::Tree rootMap; +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]; +} - template<typename T> - StackEl ¤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]; +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(); } - Q_ASSERT_X(false, "currentEl", "Stack does not contan object of type "); - return nodeStack.last(); - } + 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; - template<typename T> - T ¤t(int idx = 0) - { - return std::get<T>(currentEl<T>(idx).item.value); + default: + qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k); + Q_UNREACHABLE(); + break; } + return createMap(base, relative, n); +} - index_type currentIndex() { return currentNodeEl().path.last().headIndex(); } +QQmlDomAstCreator::QQmlDomAstCreator(const MutableDomItem &qmlFile) + : qmlFile(qmlFile), + qmlFilePtr(qmlFile.ownerAs<QmlFile>()), + rootMap(qmlFilePtr->fileLocationsTree()) +{ +} - StackEl ¤tQmlObjectOrComponentEl(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 == DomType::QmlObject || k == DomType::QmlComponent) - return nodeStack[i]; +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); } - Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack"); - return nodeStack.last(); } + // 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); - StackEl ¤tNodeEl(int i = 0) - { - Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", - "Stack does not contain element!"); - return nodeStack[nodeStack.size() - i - 1]; + if (loadDependencies) + envPtr->loadModuleDependency(i.uri.moduleUri(), i.version, DomItem::Callback()); } - - DomValue ¤tNode(int i = 0) - { - Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", - "Stack does not contain element!"); - return nodeStack[nodeStack.size() - i - 1].item; + if (m_loadFileLazily && loadDependencies) { + envPtr->loadPendingDependencies(); + envPtr->commitToBase(qmlFile.environment().item()); } - void 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(); + 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"); +} - void pushEl(Path p, DomValue it, AST::Node *n) - { - nodeStack.append({ p, it, createMap(it.kind, p, n) }); +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; +} - FileLocations::Tree createMap(FileLocations::Tree base, Path p, AST::Node *n) - { - FileLocations::Tree res = FileLocations::ensure(base, p, AttachedInfo::PathType::Relative); - if (n) - FileLocations::addRegion(res, QString(), combineLocations(n)); - return res; +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()); } - FileLocations::Tree createMap(DomType k, Path p, AST::Node *n) - { - 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))) - p = p.mid(p.length() - 2, 2); - else if (p.last().checkHeadName(Fields::value) - && p.last().headKind() == Path::Kind::Field) - p = 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: - base = currentNodeEl().fileLocations; - break; - case DomType::QmlComponent: - case DomType::Pragma: - case DomType::Import: - case DomType::Id: - case DomType::EnumDecl: - base = rootMap; - break; - case DomType::Binding: - case DomType::PropertyDefinition: - case DomType::MethodInfo: - base = currentEl<QmlObject>().fileLocations; - if (p.length() > 3) - p = p.mid(p.length() - 3, 3); - break; - default: - qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k); - Q_UNREACHABLE(); - break; + 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; } - return createMap(base, p, n); - } - -public: - QmlDomAstCreator(MutableDomItem qmlFile) - : qmlFile(qmlFile), - qmlFilePtr(qmlFile.ownerAs<QmlFile>()), - rootMap(qmlFilePtr->fileLocationsTree()) - { - } - - bool visit(UiProgram *program) override - { - 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, QString(), combineLocations(program)); - pushEl(p, *cPtr, program); - // implicit imports - // add implicit directory import - if (!fInfo.canonicalPath().isEmpty()) { - Import selfDirImport(QmlUri::fromDirectoryString(fInfo.canonicalPath())); - selfDirImport.implicit = true; - qmlFilePtr->addImport(selfDirImport); - } - for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) { - i.implicit = true; - qmlFilePtr->addImport(i); - } - return true; - } - - void endVisit(AST::UiProgram *) override - { - 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? - } + 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; } - *newC.mutableAs<QmlComponent>() = comp; - removeCurrentNode(DomType::QmlComponent); - Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack"); - } - - bool visit(UiPragma *el) override - { - createMap(DomType::Pragma, qmlFilePtr->addPragma(Pragma(el->name.toString())), el); - return true; - } - - bool visit(UiImport *el) override - { - 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(); - if (el->importUri != nullptr) - createMap(DomType::Import, - qmlFilePtr->addImport(Import::fromUriString(toString(el->importUri), v, - el->importId.toString())), - el); - else - createMap(DomType::Import, - qmlFilePtr->addImport(Import::fromFileString( - el->fileName.toString(), - el->importId.toString())), - el); - return true; - } - - bool visit(AST::UiPublicMember *el) override - { - 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, u"signal", el->propertyToken()); - 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, QString(), combineLocations(args)); - args = args->next; - } - break; + 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()); } - 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); - pushEl(pPathFromOwner, *pPtr, el); - FileLocations::addRegion(nodeStack.last().fileLocations, u"property", - el->propertyToken()); - if (p.name == u"id") - qmlFile.addError( - myParseErrors() - .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, u"default", - el->defaultToken()); - if (p.isRequired) - FileLocations::addRegion(nodeStack.last().fileLocations, u"required", - el->requiredToken()); - 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, u"colon", el->colonToken); - FileLocations::Tree valueLoc = FileLocations::ensure( - bLoc, Path::Field(Fields::value), AttachedInfo::PathType::Relative); - FileLocations::addRegion(valueLoc, QString(), combineLocations(el->statement)); - } - break; + if (p.isRequired) { + FileLocations::addRegion(nodeStack.last().fileLocations, RequiredKeywordRegion, + el->requiredToken()); } + if (p.isReadonly) { + FileLocations::addRegion(nodeStack.last().fileLocations, ReadonlyKeywordRegion, + el->readonlyToken()); } - return true; - } - - void endVisit(AST::UiPublicMember *el) override - { - 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); - } - } - } + 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 }); } - QmlObject &obj = current<QmlObject>(); - StackEl &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(); + 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({}); } - - bool visit(AST::UiSourceElement *el) override - { - QStringView code(qmlFilePtr->code()); - if (FunctionDeclaration *fDef = cast<FunctionDeclaration *>(el->sourceElement)) { - 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; - if (fDef->body) { - SourceLocation bodyLoc = combineLocations(fDef->body); - SourceLocation methodLoc = combineLocations(el); - 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); - } - 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->lparenToken.length != 0) - FileLocations::addRegion(fLoc, u"leftParen", fDef->lparenToken); - if (fDef->rparenToken.length != 0) - FileLocations::addRegion(fLoc, u"rightParen", fDef->rparenToken); - if (fDef->lbraceToken.length != 0) - FileLocations::addRegion(fLoc, u"leftBrace", fDef->lbraceToken); - if (fDef->rbraceToken.length != 0) - FileLocations::addRegion(fLoc, u"rightBrace", fDef->rbraceToken); - loadAnnotations(el); - 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); + 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); } - 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; - } - 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, QString(), combineLocations(args)); - args = args->next; } - return false; - } else { - qCWarning(creatorLog) << "unhandled source el:" << static_cast<AST::Node *>(el); - Q_UNREACHABLE(); } - return true; } - - void endVisit(AST::UiSourceElement *) override - { - MethodInfo &m = std::get<MethodInfo>(currentNode().value); - QmlObject &obj = current<QmlObject>(); - MethodInfo *mPtr = - valueFromMultimap(obj.m_methods, m.name, nodeStack.last().path.last().headIndex()); + 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; - removeCurrentNode(DomType::MethodInfo); - } - - void loadAnnotations(UiObjectMember *el) { Node::accept(el->annotations, this); } - - bool visit(AST::UiObjectDefinition *el) override - { - 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"); - } + } break; + default: + Q_UNREACHABLE(); + } + removeCurrentNode({}); +} + +void QQmlDomAstCreator::endVisit(AST::FormalParameterList *list) +{ + endVisitForLists(list); +} + +bool QQmlDomAstCreator::visit(AST::FunctionExpression *) +{ + ++m_nestedFunctionDepth; + if (!m_enableScriptExpressions) + return false; + + return true; +} + +ScriptElementVariant QQmlDomAstCreator::prepareBodyForFunction(AST::FunctionExpression *fExpression) +{ + Q_ASSERT(!scriptNodeStack.isEmpty() || !fExpression->body); + + if (fExpression->body) { + 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(fExpression->lbraceToken, fExpression->rbraceToken)); + body->setStatements(currentScriptNodeEl().takeList()); + if (auto semanticScope = body->statements().semanticScope()) + body->setSemanticScope(semanticScope); + auto result = ScriptElementVariant::fromElement(body); + removeCurrentScriptNode({}); + return result; } 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(); - } + auto result = currentScriptNodeEl().takeVariant(); + removeCurrentScriptNode({}); + return result; } - Q_ASSERT_X(sPtr, className, "could not recover new scope"); - pushEl(sPathFromOwner, *sPtr, el); - loadAnnotations(el); - return true; - } - - void endVisit(AST::UiObjectDefinition *) override - { - 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; + Q_UNREACHABLE_RETURN({}); + } + + // for convenience purposes: insert an empty BlockStatement + auto body = std::make_shared<ScriptElements::BlockStatement>( + combineLocations(fExpression->lbraceToken, fExpression->rbraceToken)); + return ScriptElementVariant::fromElement(body); +} + +void QQmlDomAstCreator::endVisit(AST::FunctionExpression *fExpression) +{ + --m_nestedFunctionDepth; + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(fExpression, DomType::ScriptFunctionExpression); + if (fExpression->identifierToken.isValid()) + current->addLocation(IdentifierRegion, fExpression->identifierToken); + if (fExpression->functionToken.isValid()) + current->addLocation(FunctionKeywordRegion, fExpression->functionToken); + if (fExpression->lparenToken.isValid()) + current->addLocation(LeftParenthesisRegion, fExpression->lparenToken); + if (fExpression->rparenToken.isValid()) + current->addLocation(RightParenthesisRegion, fExpression->rparenToken); + if (fExpression->lbraceToken.isValid()) + current->addLocation(LeftBraceRegion, fExpression->lbraceToken); + if (fExpression->rbraceToken.isValid()) + current->addLocation(RightBraceRegion, fExpression->rbraceToken); + if (fExpression->typeAnnotation) { + current->addLocation(TypeIdentifierRegion, + combineLocations(fExpression->typeAnnotation->type)); + } + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() && fExpression->body); + current->insertChild(Fields::body, prepareBodyForFunction(fExpression)); + + if (fExpression->typeAnnotation) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::returnType, currentScriptNodeEl().takeVariant()); + scriptNodeStack.removeLast(); + } + if (fExpression->formals) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList()); + current->insertChild(Fields::parameters, currentScriptNodeEl().takeList()); + scriptNodeStack.removeLast(); + } + + if (!fExpression->name.isEmpty()) + current->insertValue(Fields::name, fExpression->name); + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::FunctionDeclaration *fDef) +{ + // Treat nested functions as (named) lambdas instead of Qml Object methods. + if (m_nestedFunctionDepth > 0) { + return visit(static_cast<FunctionExpression *>(fDef)); + } + ++m_nestedFunctionDepth; + 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) +{ + // Treat nested functions as (named) lambdas instead of Qml Object methods. + if (m_nestedFunctionDepth > 1) { + endVisit(static_cast<FunctionExpression *>(fDef)); + return; + } + --m_nestedFunctionDepth; + 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; + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() && fDef->body); + m.body->setScriptElement( + finalizeScriptExpression(prepareBodyForFunction(fDef), 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(!stackHasScriptVariant()); + ScriptElementVariant variant = currentScriptNodeEl().takeVariant(); + finalizeScriptExpression(variant, pathToReturnType, argLoc); + m.returnType->setScriptElement(variant); + removeCurrentScriptNode({}); + } + if (fDef->formals) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList()); + auto parameterList = scriptNodeStack.takeLast().takeList(); + const auto parameterQList = parameterList.qList(); + size_t size = (size_t)parameterQList.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); + + ScriptElementVariant variant = parameterQList[idx]; + setFormalParameterKind(variant); + finalizeScriptExpression(variant, pathToArgument, argLoc); + m.parameters[idx].value->setScriptElement(variant); + } + } + + // 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 as last node on the stack"); + Q_ASSERT_X(false, className, + "expected an array binding with a valid QList<QmlScope> as value"); } } 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_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); - } - - bool visit(AST::UiObjectBinding *el) override - { - 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(myParseErrors() - .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)); - pushEl(bPathFromOwner, *bPtr, el); - FileLocations::addRegion(nodeStack.last().fileLocations, u"colon", el->colonToken); - loadAnnotations(el); - QmlObject *objValue = bPtr->objectValue(); - Q_ASSERT_X(objValue, className, "could not recover objectValue"); - pushEl(bPathFromOwner.field(Fields::value), *objValue, el->initializer); - return true; - } - - void endVisit(AST::UiObjectBinding *) override - { - 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 visit(AST::UiScriptBinding *el) override - { - 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)) { - StackEl &containingObjectEl = currentEl<QmlObject>(); - QmlObject &containingObject = std::get<QmlObject>(containingObjectEl.item.value); - QString idName = iExp->name.toString(); - Id idVal(idName, qmlFile.canonicalPath().path(containingObject.pathFromOwner())); - containingObject.setIdStr(idName); - FileLocations::addRegion(containingObjectEl.fileLocations, u"idToken", - combineLocations(el->qualifiedId)); - FileLocations::addRegion(containingObjectEl.fileLocations, u"idColon", - el->colonToken); - FileLocations::addRegion(containingObjectEl.fileLocations, u"id", - 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( - myParseErrors() - .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( - myParseErrors() + } + 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); + if (el->hasOnToken) + FileLocations::addRegion(nodeStack.last().fileLocations, OnTokenRegion, el->colonToken); + else + 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 " - "%2, assuming they refer to a property") - .arg(script->code(), script->astRelocatableDump())) - .withPath(pathFromOwner)); + "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))); } - 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, u"colon", el->colonToken); - return false; - } - - void endVisit(AST::UiScriptBinding *) override - { - DomValue &lastEl = currentNode(); - index_type idx = currentIndex(); - if (lastEl.kind == DomType::Binding) { - Binding &b = std::get<Binding>(lastEl.value); - 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); - QmlComponent &comp = current<QmlComponent>(); - Id *idPtr = valueFromMultimap(comp.m_ids, id.name, idx); - *idPtr = id; - } else { - Q_UNREACHABLE(); - } - removeCurrentNode({}); + } 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; +} - bool visit(AST::UiArrayBinding *el) override - { - 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( - myParseErrors() - .error(tr("id attributes should have only simple strings as values")) - .withPath(bindingPathFromOwner)); - pushEl(bindingPathFromOwner, *bindingPtr, el); - FileLocations::addRegion(currentNodeEl().fileLocations, u"colon", el->colonToken); - loadAnnotations(el); - FileLocations::Tree arrayList = - createMap(currentNodeEl().fileLocations, Path::Field(Fields::value), nullptr); - FileLocations::addRegion(arrayList, u"leftSquareBrace", el->lbracketToken); - FileLocations::addRegion(arrayList, u"rightSquareBrace", el->lbracketToken); - arrayBindingLevels.append(nodeStack.size()); - return true; - } - - void endVisit(AST::UiArrayBinding *) override - { - index_type idx = currentIndex(); - Binding &b = std::get<Binding>(currentNode().value); - Binding *bPtr = valueFromMultimap(current<QmlObject>().m_bindings, b.name(), idx); +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; - arrayBindingLevels.removeLast(); - removeCurrentNode(DomType::Binding); + } 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(); } - bool visit(AST::UiParameterList *el) override - { // currently not used... - MethodParameter p { - el->name.toString(), - el->type ? el->type->toString() : QString(), - false, false, false, {}, {}, {} - }; - return true; - } - void endVisit(AST::UiParameterList *el) override - { - Node::accept(el->next, this); // put other args at the same level as this one... - } - - bool visit(AST::UiQualifiedId *) override { return false; } - - bool visit(AST::UiEnumDeclaration *el) override - { - EnumDecl eDecl; - eDecl.setName(el->name.toString()); - EnumDecl *ePtr; - Path enumPathFromOwner = - current<QmlComponent>().addEnumeration(eDecl, AddOption::KeepExisting, &ePtr); - pushEl(enumPathFromOwner, *ePtr, el); - loadAnnotations(el); - return true; - } - - void endVisit(AST::UiEnumDeclaration *) override - { - 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 visit(AST::UiEnumMemberList *el) override - { - EnumItem it(el->member.toString(), el->value); - EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value); - Path itPathFromDecl = eDecl.addValue(it); - FileLocations::addRegion(createMap(DomType::EnumItem, itPathFromDecl, nullptr), QString(), - combine(el->memberToken, el->valueToken)); - return true; - } - - void endVisit(AST::UiEnumMemberList *el) override - { - Node::accept(el->next, this); // put other enum members at the same level as this one... - } - - bool visit(AST::UiInlineComponent *el) override - { - 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); - pushEl(p, *compPtr, el); - FileLocations::addRegion(nodeStack.last().fileLocations, u"component", el->componentToken); - loadAnnotations(el); - return true; - } - - void endVisit(AST::UiInlineComponent *) override - { - 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->m_components, key, currentIndex()); - Q_ASSERT(cPtr); - *cPtr = component; - removeCurrentNode(DomType::QmlComponent); - } - - bool visit(UiRequired *el) override - { - 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 visit(AST::UiAnnotation *el) override - { - 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(); + // 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); +} + +void QQmlDomAstCreator::endVisit(AST::ArgumentList *list) +{ + endVisitForLists(list); +} + +bool QQmlDomAstCreator::visit(AST::UiParameterList *) +{ + return false; // do not create script node for Ui stuff +} + +void QQmlDomAstCreator::endVisit(AST::PatternElementList *list) +{ + endVisitForLists<AST::PatternElementList>(list, [](AST::PatternElementList *current) { + int toCollect = 0; + toCollect += bool(current->elision); + toCollect += bool(current->element); + return toCollect; + }); +} + +void QQmlDomAstCreator::endVisit(AST::PatternPropertyList *list) +{ + endVisitForLists(list); +} + +/*! + \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, + el->valueToken.isValid() ? EnumItem::ValueKind::ExplicitValue + : EnumItem::ValueKind::ImplicitValue); + 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"))); +} + +void QQmlDomAstCreator::endVisit(AST::StatementList *list) +{ + endVisitForLists(list); +} + +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(!stackHasScriptVariant()); + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptList()); + 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(!stackHasScriptVariant()); + current->setBody(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->setExpression(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->condition) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->setCondition(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->declarations) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList()); + 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 (auto pe = forStatement->declarations->declaration; + pe && pe->declarationKindToken.isValid()) { + current->addLocation(FileLocationRegion::TypeIdentifierRegion, + pe->declarationKindToken); } - pushEl(pathFromOwner, *aPtr, el); - return true; } - void endVisit(AST::UiAnnotation *) override - { - 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(); + if (forStatement->initialiser) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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; +} + +template<typename T> +void QQmlDomAstCreator::endVisitForLists(T *list, + const std::function<int(T *)> &scriptElementsPerEntry) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptList(list); + for (auto it = list; it; it = it->next) { + const int entriesToCollect = scriptElementsPerEntry ? scriptElementsPerEntry(it) : 1; + for (int i = 0; i < entriesToCollect; ++i) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + auto last = scriptNodeStack.takeLast(); + if (last.isList()) + current.append(last.takeList()); + else + current.append(last.takeVariant()); } - removeCurrentNode(DomType::QmlObject); } - void throwRecursionDepthError() override - { - qmlFile.addError(myParseErrors().error( - tr("Maximum statement or expression depth exceeded in QmlDomAstCreator"))); + current.reverse(); + pushScriptElement(current); +} + +void QQmlDomAstCreator::endVisit(AST::VariableDeclarationList *list) +{ + endVisitForLists(list); +} + +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(!stackHasScriptVariant()); + current->insertChild(Fields::initializer, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (pe->typeAnnotation) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::type, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (pe->bindingTarget) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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); + current->addLocation(IfKeywordRegion, ifStatement->ifToken); + + if (ifStatement->ko) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->setAlternative(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + if (ifStatement->ok) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->setConsequence(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (ifStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + current->setExpression(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::YieldExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::YieldExpression *yExpression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(yExpression, DomType::ScriptYieldExpression); + current->addLocation(YieldKeywordRegion, yExpression->yieldToken); + + if (yExpression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::expression, 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(!stackHasScriptVariant()); + 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 createDom(MutableDomItem qmlFile) +void QQmlDomAstCreator::endVisit(AST::ArrayMemberExpression *expression) { - if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) { - QmlDomAstCreator componentCreator(qmlFile); - AST::Node::accept(qmlFilePtr->ast(), &componentCreator); - AstComments::collectComments(qmlFile); + 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(!stackHasScriptVariant()); + // 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(!stackHasScriptVariant()); + 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(!stackHasScriptList()); + current->insertChild(Fields::arguments, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); } else { - qCWarning(creatorLog) << "createDom called on non qmlFile"; + // insert empty list + current->insertChild(Fields::arguments, + ScriptElements::ScriptList(exp->lparenToken, exp->rparenToken)); + } + + if (exp->base) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptList()); + 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(!stackHasScriptList()); + 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(!stackHasScriptVariant()); + 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); + current->addLocation(FileLocationRegion::TypeIdentifierRegion, statement->declarationKindToken); + + if (statement->declarations) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList()); + + 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)); + current->addLocation(FileLocationRegion::IdentifierRegion, combineLocations(exp->typeArgument)); + } + + if (exp->typeId) { + current->insertChild(Fields::typeName, fieldMemberExpressionForQualifiedId(exp->typeId)); + current->addLocation(FileLocationRegion::TypeIdentifierRegion, combineLocations(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(!stackHasScriptList()); + 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(!stackHasScriptList()); + current->insertChild(Fields::statements, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + 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(!stackHasScriptList()); + current->insertChild(Fields::moreCaseClauses, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + if (exp->defaultClause) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::defaultClause, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->clauses) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList()); + 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::SwitchKeywordRegion, exp->switchToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->block) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::caseBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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::ForKeywordRegion, exp->forToken); + 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(!stackHasScriptVariant()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->lhs) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::bindingElement, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + + if (auto pe = AST::cast<PatternElement *>(exp->lhs); + pe && pe->declarationKindToken.isValid()) { + current->addLocation(FileLocationRegion::TypeIdentifierRegion, + pe->declarationKindToken); + } + } + + 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(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + current->insertChild(Fields::catchBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::catchParameter, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (statement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (commaExpression->left) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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(!stackHasScriptVariant()); + current->insertChild(Fields::alternative, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->ok) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + current->insertChild(Fields::consequence, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); + 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 (!stackHasScriptVariant()) { + 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(!stackHasScriptVariant()); + 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; + case DomType::ScriptFunctionExpression: { + // Put the body's scope into the function expression: function expressions will contain + // their parents scope instead of their own without this + auto element = m_domCreator.currentScriptNodeEl().value; + auto scriptElementVariant = std::get_if<ScriptElementVariant>(&element); + if (!scriptElementVariant || !scriptElementVariant->data()) + break; + scriptElementVariant->visit([](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + if (e->kind() != DomType::ScriptFunctionExpression) + return; + + if constexpr (std::is_same_v<U, + ScriptElement::PointerType< + ScriptElements::GenericScriptElement>>) { + if (auto bodyPtr = e->elementChild(Fields::body)) { + const auto bodyScope = bodyPtr.base()->semanticScope(); + e->setSemanticScope(bodyScope); + } + } + }); + 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. + const bool usePropertyDefinitionScopeInsteadOfTheBindingScope = + scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope + && scope->parentScope() + && scope->parentScope()->scopeType() == QQmlSA::ScopeType::QMLScope; + e.setSemanticScope(usePropertyDefinitionScopeInsteadOfTheBindingScope + ? scope->parentScope() + : scope); + } + }, + 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 index 528304b83f..8a050b4c2d 100644 --- a/src/qmldom/qqmldomastcreator_p.h +++ b/src/qmldom/qqmldomastcreator_p.h @@ -15,22 +15,728 @@ // We mean it. // -#include "qqmldom_global.h" +#include "qqmldomelements_p.h" #include "qqmldomitem_p.h" -#include "qqmldomastcreator_p.h" -#include "qqmldomcomments_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 { -SourceLocation combineLocations(SourceLocation s1, SourceLocation s2); -SourceLocation combineLocations(AST::Node *n); +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; + int m_nestedFunctionDepth = 0; + 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; + +private: + ScriptElementVariant prepareBodyForFunction(AST::FunctionExpression *fExpression); + +public: + bool visit(AST::FunctionExpression *el) override; + void endVisit(AST::FunctionExpression *) 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::YieldExpression *block) override; + void endVisit(AST::YieldExpression *) 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 don't need a qqmljsscope: visitation order can be custom + bool visit(AST::UiParameterList *) override; + bool visit(AST::Elision *elision) override; + + + // lists of stuff whose children need a qqmljsscope: visitation order cannot be custom + void endVisit(AST::StatementList *list) override; + void endVisit(AST::VariableDeclarationList *vdl) override; + void endVisit(AST::ArgumentList *) override; + void endVisit(AST::PatternElementList *) override; + void endVisit(AST::PatternPropertyList *) override; + void endVisit(AST::FormalParameterList *el) 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; + + bool stackHasScriptVariant() const + { + return !scriptNodeStack.isEmpty() && !scriptNodeStack.last().isList(); + } + bool stackHasScriptList() const + { + return !scriptNodeStack.isEmpty() && scriptNodeStack.last().isList(); + } + +private: + template<typename T> + void endVisitForLists(T *list, const std::function<int(T *)> &scriptElementsPerEntry = {}); + +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, + AST::VariableDeclarationList>; + + 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 (T* 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 if constexpr (std::is_same_v<T, AST::VariableDeclarationList>) { + AST::Node::accept(it->declaration, this); + } else if constexpr (std::is_same_v<T, AST::ArgumentList>) { + AST::Node::accept(it->expression, this); + } else if constexpr (std::is_same_v<T, AST::PatternElementList>) { + AST::Node::accept(it->elision, this); + 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; -void createDom(MutableDomItem qmlFile); + std::optional<InactiveVisitorMarker> m_inactiveVisitorMarker; + bool m_enableScriptExpressions = false; + bool m_loadFileLazily = false; +}; } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomastdumper.cpp b/src/qmldom/qqmldomastdumper.cpp index ffb776cf2d..b4986f7a71 100644 --- a/src/qmldom/qqmldomastdumper.cpp +++ b/src/qmldom/qqmldomastdumper.cpp @@ -132,6 +132,14 @@ public: 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), @@ -330,7 +338,7 @@ public: void endVisit(AST::ThisExpression *) override { stop(u"ThisExpression"); } bool visit(AST::IdentifierExpression *el) override { - start(QLatin1String("IdentifierExpression name=%1 identiferToken=%2") + start(QLatin1String("IdentifierExpression name=%1 identifierToken=%2") .arg(quotedString(el->name), loc(el->identifierToken))); return true; } @@ -982,12 +990,6 @@ public: } void endVisit(AST::Type *) override { stop(u"Type"); } - bool visit(AST::TypeArgument *) override { - start(u"TypeArgument"); - return true; - } - void endVisit(AST::TypeArgument *) override { stop(u"TypeArgument"); } - bool visit(AST::TypeAnnotation *el) override { start(QLatin1String("TypeAnnotation colonToken=%1") .arg(loc(el->colonToken))); @@ -1082,7 +1084,7 @@ QString astNodeDiff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions return lineDiff(s1, s2, nContext); } -void astNodeDumper(Sink s, Node *n, AstDumperOptions opt, int indent, int baseIndent, +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); @@ -1092,7 +1094,10 @@ void astNodeDumper(Sink s, Node *n, AstDumperOptions opt, int indent, int baseIn QString astNodeDump(Node *n, AstDumperOptions opt, int indent, int baseIndent, function_ref<QStringView(SourceLocation)>loc2str) { - return dumperToString([n, opt, indent, baseIndent, loc2str](Sink s) { astNodeDumper(s, n, opt, indent, baseIndent, 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 diff --git a/src/qmldom/qqmldomastdumper_p.h b/src/qmldom/qqmldomastdumper_p.h index c942f53a0e..542f81b1dd 100644 --- a/src/qmldom/qqmldomastdumper_p.h +++ b/src/qmldom/qqmldomastdumper_p.h @@ -39,7 +39,7 @@ 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(Sink s, AST::Node *n, AstDumperOptions opt = AstDumperOption::None, +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, diff --git a/src/qmldom/qqmldomattachedinfo.cpp b/src/qmldom/qqmldomattachedinfo.cpp index 838b373048..e86a2782a6 100644 --- a/src/qmldom/qqmldomattachedinfo.cpp +++ b/src/qmldom/qqmldomattachedinfo.cpp @@ -1,12 +1,17 @@ // 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 @@ -26,120 +31,120 @@ Attributes: \sa QQmlJs::Dom::AttachedInfo */ -bool FileLocations::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool FileLocations::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; -#ifdef QmlDomAddCodeStr - bool hasCode = false; - QString codeStr = self.fileObject().field(Fields::code).value().toString(); - auto loc2str = [&self, &codeStr](SourceLocation loc) { - if (loc.offset < codeStr.length() && loc.end() <= codeStr.length()) - return QStringView(codeStr).mid(loc.offset, loc.length); - return QStringView(); - }; -#else - auto loc2str = [](SourceLocation) { return QStringView(); }; -#endif cont = cont && self.dvValueLazyField(visitor, Fields::fullRegion, [this]() { - return locationToData(fullRegion); + return sourceLocationToQCborValue(fullRegion); }); - cont = cont && self.dvItemField(visitor, Fields::regions, [this, &self, &loc2str]() { - return self.subMapItem(Map::fromMapRef<SourceLocation>( - self.pathFromOwner().field(Fields::regions), regions, - [&loc2str](DomItem &map, const PathEls::PathComponent &key, SourceLocation &el) { - return map.subLocationItem(key, el, loc2str(el)); - })); + 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, &loc2str]() { - return self.subMapItem(Map::fromMapRef<QList<SourceLocation>>( - self.pathFromOwner().field(Fields::preCommentLocations), - preCommentLocations, - [&loc2str](DomItem &map, const PathEls::PathComponent &key, - QList<SourceLocation> &el) { - return map.subListItem(List::fromQListRef<SourceLocation>( - map.pathFromOwner().appendComponent(key), el, - [&loc2str](DomItem &list, const PathEls::PathComponent &idx, - SourceLocation &el) { - return list.subLocationItem(idx, el, loc2str(el)); - })); - })); + && 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, &loc2str]() { - return self.subMapItem(Map::fromMapRef<QList<SourceLocation>>( - self.pathFromOwner().field(Fields::postCommentLocations), - postCommentLocations, - [&loc2str](DomItem &map, const PathEls::PathComponent &key, - QList<SourceLocation> &el) { - return map.subListItem(List::fromQListRef<SourceLocation>( - map.pathFromOwner().appendComponent(key), el, - [&loc2str](DomItem &list, const PathEls::PathComponent &idx, - SourceLocation &el) { - return list.subLocationItem(idx, el, loc2str(el)); - })); - })); + && 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; } -void FileLocations::ensureCommentLocations(QList<QString> keys) -{ - for (auto k : keys) { - preCommentLocations[k]; - postCommentLocations[k]; - } -} - -FileLocations::Tree FileLocations::createTree(Path basePath){ +FileLocations::Tree FileLocations::createTree(const Path &basePath){ return AttachedInfoT<FileLocations>::createTree(basePath); } -FileLocations::Tree FileLocations::ensure(FileLocations::Tree base, Path basePath, AttachedInfo::PathType pType){ +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(DomItem &item, AttachedInfo::FindOptions options) +FileLocations::findAttachedInfo(const DomItem &item) { - return AttachedInfoT<FileLocations>::findAttachedInfo(item, Fields::fileLocationsTree, options); + return AttachedInfoT<FileLocations>::findAttachedInfo(item, Fields::fileLocationsTree); } -FileLocations::Tree FileLocations::treePtr(DomItem &item) +/*! + \internal + Returns the tree corresponding to a DomItem. + */ +FileLocations::Tree FileLocations::treeOf(const DomItem &item) { - return AttachedInfoT<FileLocations>::treePtr(item, Fields::fileLocationsTree); + return findAttachedInfo(item).foundTree; } -const FileLocations *FileLocations::fileLocationsPtr(DomItem &item) +/*! + \internal + Returns the filelocation Info corresponding to a DomItem. + */ +const FileLocations *FileLocations::fileLocationsOf(const DomItem &item) { - if (FileLocations::Tree t = treePtr(item)) + if (const FileLocations::Tree &t = treeOf(item)) return &(t->info()); return nullptr; } -void FileLocations::updateFullLocation(FileLocations::Tree fLoc, SourceLocation loc) { +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()) + if (loc.begin() < l.begin() || loc.end() > l.end()) { l = combine(l, loc); - else + p->info().regions[MainRegion] = l; + } else { break; + } p = p->parent(); } } } -void FileLocations::addRegion(FileLocations::Tree fLoc, QString locName, SourceLocation loc) { +// 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[locName] = loc; + fLoc->info().regions[region] = loc; updateFullLocation(fLoc, loc); } -void FileLocations::addRegion(FileLocations::Tree fLoc, QStringView locName, SourceLocation loc) { - addRegion(fLoc, locName.toString(), 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{}; } /*! @@ -160,7 +165,7 @@ Attributes: \sa QQmlJs::Dom::AttachedInfo */ -bool AttachedInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool AttachedInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; if (Ptr p = parent()) @@ -172,13 +177,13 @@ bool AttachedInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::subItems, [this, &self]() { return self.subMapItem(Map( Path::Field(Fields::subItems), - [this](DomItem &map, QString key) { + [this](const DomItem &map, const QString &key) { Path p = Path::fromString(key); return map.copy(m_subItems.value(p), map.canonicalPath().key(key)); }, - [this](DomItem &) { + [this](const DomItem &) { QSet<QString> res; - for (auto p : m_subItems.keys()) + for (const auto &p : m_subItems.keys()) res.insert(p.toString()); return res; }, @@ -202,7 +207,9 @@ AttachedInfo::AttachedInfo(const AttachedInfo &o): The path might be either a relative path or a canonical path, as specified by the PathType */ -AttachedInfo::Ptr AttachedInfo::ensure(AttachedInfo::Ptr self, Path path, AttachedInfo::PathType pType){ +AttachedInfo::Ptr AttachedInfo::ensure( + const AttachedInfo::Ptr &self, const Path &path, AttachedInfo::PathType pType){ + Path relative; switch (pType) { case PathType::Canonical: { if (!path) @@ -210,14 +217,15 @@ AttachedInfo::Ptr AttachedInfo::ensure(AttachedInfo::Ptr self, Path path, Attach Q_ASSERT(self); Path removed = path.mid(0, self->path().length()); Q_ASSERT(removed == self->path()); - path = path.mid(self->path().length()); + relative = path.mid(self->path().length()); } break; case PathType::Relative: Q_ASSERT(self); + relative = path; break; } Ptr res = self; - for (auto p : path) { + for (const auto &p : std::as_const(relative)) { if (AttachedInfo::Ptr subEl = res->m_subItems.value(p)) { res = subEl; } else { @@ -229,15 +237,21 @@ AttachedInfo::Ptr AttachedInfo::ensure(AttachedInfo::Ptr self, Path path, Attach return res; } -AttachedInfo::Ptr AttachedInfo::find(AttachedInfo::Ptr self, Path p, AttachedInfo::PathType pType){ +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; - Path rest = p; while (rest) { if (!res) break; @@ -248,8 +262,7 @@ AttachedInfo::Ptr AttachedInfo::find(AttachedInfo::Ptr self, Path p, AttachedInf } AttachedInfoLookupResult<AttachedInfo::Ptr> -AttachedInfo::findAttachedInfo(DomItem &item, QStringView fieldName, - AttachedInfo::FindOptions options) +AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName) { Path p; DomItem fLoc = item.field(fieldName); @@ -272,60 +285,55 @@ AttachedInfo::findAttachedInfo(DomItem &item, QStringView fieldName, if (AttachedInfo::Ptr foundTree = AttachedInfo::find(fLocPtr, p, AttachedInfo::PathType::Relative)) res.foundTree = foundTree; - if (options & (FindOption::SetRootTreePath | FindOption::SetFoundTreePath)) - res.rootTreePath = fLoc.canonicalPath(); - if (options & FindOption::SetFoundTreePath) { - Path foundTreePath = res.rootTreePath.value(); - if (res.lookupPath) { - foundTreePath = foundTreePath.key(res.lookupPath.head().toString()); - for (Path pEl : res.lookupPath.mid(1)) - foundTreePath = foundTreePath.field(Fields::subItems).key(pEl.toString()); - } - res.foundTreePath = foundTreePath; - } + 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(DomItem &self, DirectVisitor visitor) +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(Path basePath) +UpdatedScriptExpression::Tree UpdatedScriptExpression::createTree(const Path &basePath) { return AttachedInfoT<UpdatedScriptExpression>::createTree(basePath); } -UpdatedScriptExpression::Tree UpdatedScriptExpression::ensure(UpdatedScriptExpression::Tree base, - Path basePath, - AttachedInfo::PathType pType) +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(DomItem &item, AttachedInfo::FindOptions options) +UpdatedScriptExpression::findAttachedInfo(const DomItem &item) { return AttachedInfoT<UpdatedScriptExpression>::findAttachedInfo( - item, Fields::updatedScriptExpressions, options); + item, Fields::updatedScriptExpressions); } -UpdatedScriptExpression::Tree UpdatedScriptExpression::treePtr(DomItem &item) +UpdatedScriptExpression::Tree UpdatedScriptExpression::treePtr(const DomItem &item) { return AttachedInfoT<UpdatedScriptExpression>::treePtr(item, Fields::updatedScriptExpressions); } -const UpdatedScriptExpression *UpdatedScriptExpression::exprPtr(DomItem &item) +const UpdatedScriptExpression *UpdatedScriptExpression::exprPtr(const DomItem &item) { if (UpdatedScriptExpression::Tree t = treePtr(item)) return &(t->info()); return nullptr; } -bool UpdatedScriptExpression::visitTree(Tree base, function_ref<bool(Path, Tree)> visitor, - Path basePath) +bool UpdatedScriptExpression::visitTree( + const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor, + const Path &basePath) { return AttachedInfoT<UpdatedScriptExpression>::visitTree(base, visitor, basePath); } diff --git a/src/qmldom/qqmldomattachedinfo_p.h b/src/qmldom/qqmldomattachedinfo_p.h index 808d0db467..8412c3ab9b 100644 --- a/src/qmldom/qqmldomattachedinfo_p.h +++ b/src/qmldom/qqmldomattachedinfo_p.h @@ -25,22 +25,25 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { +struct AttachedInfoLookupResultBase +{ + Path lookupPath; + Path rootTreePath; + Path foundTreePath; +}; template<typename TreePtr> -class AttachedInfoLookupResult +class AttachedInfoLookupResult: public AttachedInfoLookupResultBase { public: TreePtr foundTree; - Path lookupPath; // relative path used to reach result - std::optional<Path> rootTreePath; // path of the root TreePath - std::optional<Path> foundTreePath; + 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); - res.lookupPath = lookupPath; - res.rootTreePath = rootTreePath; return res; } }; @@ -53,51 +56,44 @@ public: Canonical }; Q_ENUM(PathType) - enum class FindOption { - None = 0, - SetRootTreePath = 0x1, - SetFoundTreePath = 0x2, - Default = 0x3 - }; - Q_DECLARE_FLAGS(FindOptions, FindOption) - Q_FLAG(FindOptions) constexpr static DomType kindValue = DomType::AttachedInfo; using Ptr = std::shared_ptr<AttachedInfo>; DomType kind() const override { return kindValue; } - Path canonicalPath(DomItem &self) const override { return self.m_ownerPath; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; - AttachedInfo::Ptr makeCopy(DomItem &self) const + 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(Path p) { m_path = p; } + void setPath(const Path &p) { m_path = p; } + + AttachedInfo(const Ptr &parent = nullptr, const Path &p = Path()) + : m_path(p), m_parent(parent) + {} - AttachedInfo(Ptr parent = nullptr, Path p = Path()) : m_path(p), m_parent(parent) {} AttachedInfo(const AttachedInfo &o); - static Ptr ensure(Ptr self, Path path, PathType pType = PathType::Relative); - static Ptr find(Ptr self, Path p, PathType pType = PathType::Relative); - static AttachedInfoLookupResult<Ptr> - findAttachedInfo(DomItem &item, QStringView treeFieldName, - FindOptions options = AttachedInfo::FindOption::None); - static Ptr treePtr(DomItem &item, QStringView fieldName) + 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, FindOption::None).foundTree; + return findAttachedInfo(item, fieldName).foundTree; } - DomItem itemAtPath(DomItem &self, Path p, PathType pType = PathType::Relative) const + DomItem itemAtPath(const DomItem &self, const Path &p, PathType pType = PathType::Relative) const { if (Ptr resPtr = find(self.ownerAs<AttachedInfo>(), p, pType)) { - if (pType == PathType::Canonical) - p = p.mid(m_path.length()); + const Path relative = (pType == PathType::Canonical) ? p.mid(m_path.length()) : p; Path resPath = self.canonicalPath(); - for (Path pEl : p) { + for (const Path &pEl : relative) { resPath = resPath.field(Fields::subItems).key(pEl.toString()); } return self.copy(resPtr, resPath); @@ -105,19 +101,18 @@ public: return DomItem(); } - DomItem infoAtPath(DomItem &self, Path p, PathType pType = PathType::Relative) const + 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, Path p, + MutableDomItem ensureItemAtPath(MutableDomItem &self, const Path &p, PathType pType = PathType::Relative) { if (Ptr resPtr = ensure(self.ownerAs<AttachedInfo>(), p, pType)) { - if (pType == PathType::Canonical) - p = p.mid(m_path.length()); + const Path relative = (pType == PathType::Canonical) ? p.mid(m_path.length()) : p; Path resPath = self.canonicalPath(); - for (Path pEl : p) { + for (const Path &pEl : relative) { resPath = resPath.field(Fields::subItems).key(pEl.toString()); } return MutableDomItem(self.item().copy(resPtr, resPath)); @@ -125,18 +120,15 @@ public: return MutableDomItem(); } - MutableDomItem ensureInfoAtPath(MutableDomItem &self, Path p, + MutableDomItem ensureInfoAtPath(MutableDomItem &self, const Path &p, PathType pType = PathType::Relative) { return ensureItemAtPath(self, p, pType).field(Fields::infoItem); } - virtual AttachedInfo::Ptr instantiate(AttachedInfo::Ptr parent, Path p = Path()) const = 0; - virtual DomItem infoItem(DomItem &self) = 0; - DomItem infoItem(DomItem &self) const - { - return const_cast<AttachedInfo *>(this)->infoItem(self); - } + 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; } @@ -148,7 +140,6 @@ protected: std::weak_ptr<AttachedInfo> m_parent; QMap<Path, Ptr> m_subItems; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(AttachedInfo::FindOptions) template<typename Info> class QMLDOM_EXPORT AttachedInfoT final : public AttachedInfo @@ -158,7 +149,7 @@ public: using Ptr = std::shared_ptr<AttachedInfoT>; using InfoType = Info; - AttachedInfoT(Ptr parent = nullptr, Path p = Path()) : AttachedInfo(parent, p) {} + AttachedInfoT(const Ptr &parent = nullptr, const Path &p = Path()) : AttachedInfo(parent, p) {} AttachedInfoT(const AttachedInfoT &o): AttachedInfo(o), m_info(o.m_info) @@ -171,29 +162,32 @@ public: } } - static Ptr createTree(Path p = Path()) { + static Ptr createTree(const Path &p = Path()) { return Ptr(new AttachedInfoT(nullptr, p)); } - static Ptr ensure(Ptr self, Path path, PathType pType = PathType::Relative){ + 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(Ptr self, Path p, PathType pType = PathType::Relative){ + 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(DomItem &item, QStringView fieldName, - AttachedInfo::FindOptions options) + static AttachedInfoLookupResult<Ptr> findAttachedInfo(const DomItem &item, + QStringView fieldName) { - return AttachedInfo::findAttachedInfo(item, fieldName, options) - .template as<AttachedInfoT>(); + return AttachedInfo::findAttachedInfo(item, fieldName).template as<AttachedInfoT>(); } - static Ptr treePtr(DomItem &item, QStringView fieldName) + static Ptr treePtr(const DomItem &item, QStringView fieldName) { return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::treePtr(item, fieldName)); } - static bool visitTree(Ptr base, function_ref<bool(Path, Ptr)>visitor, Path basePath = Path()) { + 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)) { @@ -211,12 +205,15 @@ public: return true; } - AttachedInfo::Ptr instantiate(AttachedInfo::Ptr parent, Path p = Path()) const override { + 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(DomItem &self) override { return self.wrapField(Fields::infoItem, m_info); } - Ptr makeCopy(DomItem &self) const + 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)); } @@ -225,8 +222,18 @@ public: 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(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { return Ptr(new AttachedInfoT(*this)); } @@ -240,34 +247,34 @@ public: using Tree = std::shared_ptr<AttachedInfoT<FileLocations>>; constexpr static DomType kindValue = DomType::FileLocations; DomType kind() const { return kindValue; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor); - void ensureCommentLocations(QList<QString> keys); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; - static Tree createTree(Path basePath); - static Tree ensure(Tree base, Path basePath, AttachedInfo::PathType pType); - static Tree find(Tree self, Path p, + 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(DomItem &item, - AttachedInfo::FindOptions options = AttachedInfo::FindOption::Default); - // convenience: find FileLocations::Tree attached to the given item - static FileLocations::Tree treePtr(DomItem &); - // convenience: find FileLocations* attached to the given item (if there is one) - static const FileLocations *fileLocationsPtr(DomItem &); + static AttachedInfoLookupResult<Tree> findAttachedInfo(const DomItem &item); + static FileLocations::Tree treeOf(const DomItem &); + static const FileLocations *fileLocationsOf(const DomItem &); - static void updateFullLocation(Tree fLoc, SourceLocation loc); - static void addRegion(Tree fLoc, QString locName, SourceLocation loc); - static void addRegion(Tree fLoc, QStringView locName, SourceLocation loc); + 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<QString, SourceLocation> regions; - QMap<QString, QList<SourceLocation>> preCommentLocations; - QMap<QString, QList<SourceLocation>> postCommentLocations; + QMap<FileLocationRegion, SourceLocation> regions; + QMap<FileLocationRegion, QList<SourceLocation>> preCommentLocations; + QMap<FileLocationRegion, QList<SourceLocation>> postCommentLocations; }; class QMLDOM_EXPORT UpdatedScriptExpression @@ -277,22 +284,22 @@ public: using Tree = std::shared_ptr<AttachedInfoT<UpdatedScriptExpression>>; constexpr static DomType kindValue = DomType::UpdatedScriptExpression; DomType kind() const { return kindValue; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; - static Tree createTree(Path basePath); - static Tree ensure(Tree base, Path basePath, AttachedInfo::PathType pType); + 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(DomItem &item, - AttachedInfo::FindOptions options = AttachedInfo::FindOption::Default); + findAttachedInfo(const DomItem &item); // convenience: find FileLocations::Tree attached to the given item - static Tree treePtr(DomItem &); + static Tree treePtr(const DomItem &); // convenience: find FileLocations* attached to the given item (if there is one) - static const UpdatedScriptExpression *exprPtr(DomItem &); + static const UpdatedScriptExpression *exprPtr(const DomItem &); - static bool visitTree(Tree base, function_ref<bool(Path, Tree)> visitor, - Path basePath = Path()); + static bool visitTree( + const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor, + const Path &basePath = Path()); std::shared_ptr<ScriptExpression> expr; }; diff --git a/src/qmldom/qqmldomcodeformatter.cpp b/src/qmldom/qqmldomcodeformatter.cpp index 9997905a5e..27279ce7c0 100644 --- a/src/qmldom/qqmldomcodeformatter.cpp +++ b/src/qmldom/qqmldomcodeformatter.cpp @@ -6,7 +6,7 @@ #include <QLoggingCategory> #include <QMetaEnum> -static Q_LOGGING_CATEGORY(formatterLog, "qt.qmldom.formatter", QtWarningMsg); +Q_STATIC_LOGGING_CATEGORY(formatterLog, "qt.qmldom.formatter", QtWarningMsg); QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -224,9 +224,8 @@ void FormatPartialStatus::dump() const { qCDebug(formatterLog) << "Current token index" << tokenIndex; qCDebug(formatterLog) << "Current state:"; - foreach (const State &s, currentStatus.states) { + 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; } diff --git a/src/qmldom/qqmldomcomments.cpp b/src/qmldom/qqmldomcomments.cpp index 3eee7adb28..8e94758b57 100644 --- a/src/qmldom/qqmldomcomments.cpp +++ b/src/qmldom/qqmldomcomments.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// 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" @@ -17,7 +17,7 @@ #include <variant> -static Q_LOGGING_CATEGORY(commentsLog, "qt.qmldom.comments", QtWarningMsg); +Q_STATIC_LOGGING_CATEGORY(commentsLog, "qt.qmldom.comments", QtWarningMsg); QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -65,7 +65,8 @@ 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) +CommentInfo::CommentInfo(QStringView rawComment, QQmlJS::SourceLocation loc) + : rawComment(rawComment), commentLocation(loc) { commentBegin = 0; while (commentBegin < quint32(rawComment.size()) && rawComment.at(commentBegin).isSpace()) { @@ -97,10 +98,9 @@ CommentInfo::CommentInfo(QStringView rawComment) : rawComment(rawComment) warnings.append(tr("Unexpected comment start %1").arg(commentStartStr)); break; } + commentEnd = commentBegin + commentStartStr.size(); quint32 rawEnd = quint32(rawComment.size()); - while (commentEnd < rawEnd && rawComment.at(commentEnd).isSpace()) - ++commentEnd; commentContentEnd = commentContentBegin = commentEnd; QChar e1 = ((expectedEnd.isEmpty()) ? QChar::fromLatin1(0) : expectedEnd.at(0)); while (commentEnd < rawEnd) { @@ -115,7 +115,8 @@ CommentInfo::CommentInfo(QStringView rawComment) : rawComment(rawComment) commentContentEnd = commentEnd; } } else { - commentEndStr = rawComment.mid(++commentEnd - 1, 1); + // 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()) { @@ -156,6 +157,11 @@ CommentInfo::CommentInfo(QStringView rawComment) : rawComment(rawComment) .arg(i)); } } + + // Post process comment source location + commentLocation.offset -= commentStartStr.size(); + commentLocation.startColumn -= commentStartStr.size(); + commentLocation.length = commentEnd - commentBegin; } /*! @@ -191,7 +197,7 @@ A comment has methods to write it out again (write) and expose it to the Dom /*! \brief Expose attributes to the Dom */ -bool Comment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Comment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::rawComment, rawComment()); @@ -236,55 +242,39 @@ Every region has a name, and should be written out using the OutWriter.writeRegi startRegion/ EndRegion). Region comments keeps a mapping containing them. */ -bool CommentedElement::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool CommentedElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; - cont = cont && self.dvWrapField(visitor, Fields::preComments, preComments); - cont = cont && self.dvWrapField(visitor, Fields::postComments, postComments); + 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(preComments.size()); + locs->resize(m_preComments.size()); int i = 0; - for (const Comment &c : preComments) + 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(postComments.size()); + locs->resize(m_postComments.size()); int i = 0; - for (const Comment &c : postComments) + for (const Comment &c : m_postComments) c.write(lw, (locs ? &((*locs)[i++]) : nullptr)); } -/*! -\brief Given the SourceLocation of the current element returns the comments associated with the -start and end of item - -The map uses an index that is based on 2*the location. Thus for every location l it is possible -to have two indexes: 2*l (just before) and 2*l+1 (just after). -This allows to attach comments to indexes representing either just before or after any location -*/ -QMultiMap<quint32, const QList<Comment> *> -CommentedElement::commentGroups(SourceLocation elLocation) const -{ - return QMultiMap<quint32, const QList<Comment> *>( - { { elLocation.begin() * 2, &preComments }, - { elLocation.end() * 2 + 1, &postComments } }); -} - using namespace QQmlJS::AST; class RegionRef { public: Path path; // store the MutableDomItem instead? - QString regionName; + FileLocationRegion regionName; }; // internal class to keep a reference either to an AST::Node* or a region of a DomItem and the @@ -293,8 +283,8 @@ class ElementRef { public: ElementRef(AST::Node *node, quint32 size) : element(node), size(size) { } - ElementRef(Path path, QString region, quint32 size) - : element(RegionRef { path, region }), size(size) + ElementRef(const Path &path, FileLocationRegion region, quint32 size) + : element(RegionRef{ path, region }), size(size) { } operator bool() const @@ -331,6 +321,9 @@ QSet<int> VisitAll::uiKinds() 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, @@ -346,11 +339,13 @@ public: AstRangesVisitor() = default; void addNodeRanges(AST::Node *rootNode); - void addItemRanges(DomItem item, FileLocations::Tree itemLocations, Path currentP); + 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 { @@ -365,8 +360,6 @@ public: return true; } - QQmlJS::Engine *engine; - FileLocations::Tree rootItemLocations; QMap<quint32, ElementRef> starts; QMap<quint32, ElementRef> ends; }; @@ -376,7 +369,8 @@ void AstRangesVisitor::addNodeRanges(AST::Node *rootNode) AST::Node::accept(rootNode, this); } -void AstRangesVisitor::addItemRanges(DomItem item, FileLocations::Tree itemLocations, Path currentP) +void AstRangesVisitor::addItemRanges( + const DomItem &item, const FileLocations::Tree &itemLocations, const Path ¤tP) { if (!itemLocations) { if (item) @@ -389,10 +383,13 @@ void AstRangesVisitor::addItemRanges(DomItem item, FileLocations::Tree itemLocat for (auto it = regs.cbegin(), end = regs.cend(); it != end; ++it) { quint32 startI = it.value().begin(); quint32 endI = it.value().end(); - 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 }); + + 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 }); + } } } { @@ -425,88 +422,99 @@ const QSet<int> AstRangesVisitor::kindsToSkip() return res; } -/*! -\class QQmlJS::Dom::AstComments -\brief Stores the comments associated with javascript AST::Node pointers +/*! \internal + \brief returns true if comments should skip attaching to this region */ - -bool AstComments::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool AstRangesVisitor::shouldSkipRegion(const DomItem &item, FileLocationRegion region) { - bool cont = self.dvItemField(visitor, Fields::commentedElements, [this, &self]() { - return self.subMapItem(Map( - self.pathFromOwner().field(Fields::commentedElements), - [this](DomItem &map, QString key) { - bool ok; - // we expose the comments as map just for debugging purposes, - // as key we use the address hex value as key (keys must be strings) - quintptr v = key.split(QLatin1Char('_')).last().toULong(&ok, 16); - // recover the actual key, and check if it is in the map - AST::Node *n = reinterpret_cast<AST::Node *>(v); - if (ok && m_commentedElements.contains(n)) - return map.wrap(PathEls::Key(key), m_commentedElements[n]); - return DomItem(); - }, - [this](DomItem &) { - QSet<QString> res; - for (AST::Node *n : m_commentedElements.keys()) { - QString name; - if (n) - name = QString::number(n->kind); // we should add mapping to - // string for this - res.insert(name + QStringLiteral(u"_") + QString::number(quintptr(n), 16)); - } - return res; - }, - QLatin1String("CommentedElements"))); - }); - return cont; + 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); } -void AstComments::collectComments(MutableDomItem &item) +class CommentLinker { - if (std::shared_ptr<ScriptExpression> scriptPtr = item.ownerAs<ScriptExpression>()) { - DomItem itemItem = item.item(); - return collectComments(scriptPtr->engine(), scriptPtr->ast(), scriptPtr->astComments(), - item, FileLocations::treePtr(itemItem)); - } else if (std::shared_ptr<QmlFile> qmlFilePtr = item.ownerAs<QmlFile>()) { - return collectComments(qmlFilePtr->engine(), qmlFilePtr->ast(), qmlFilePtr->astComments(), - item, qmlFilePtr->fileLocationsTree()); - } else { - qCWarning(commentsLog) - << "collectComments works with QmlFile and ScriptExpression, not with" - << item.internalKindStr(); +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()} + { } -} -/*! -\brief -Collects and associates comments with javascript AST::Node pointers and MutableDomItem in -rootItem -*/ -void AstComments::collectComments(std::shared_ptr<Engine> engine, AST::Node *n, - std::shared_ptr<AstComments> ccomm, MutableDomItem rootItem, - FileLocations::Tree rootItemLocations) -{ - if (!n) - return; - AstRangesVisitor ranges; - ranges.addItemRanges(rootItem.item(), rootItemLocations, Path()); - ranges.addNodeRanges(n); - QStringView code = engine->code(); - QHash<AST::Node *, CommentedElement> &commentedElements = ccomm->m_commentedElements; - quint32 lastPostCommentPostEnd = 0; - for (SourceLocation cLoc : engine->comments()) { - // collect whitespace before and after cLoc -> iPre..iPost contains whitespace, - // do not add newline before, but add the one after - quint32 iPre = cLoc.begin(); + 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 = code.at(iPre - 1); + QChar c = m_code.at(iPre - 1); if (!c.isSpace()) { if (commentStartStr.isEmpty() && (c == QLatin1Char('*') || c == QLatin1Char('/')) - && iPre - 1 > 0 && code.at(iPre - 2) == QLatin1Char('/')) { - commentStartStr = code.mid(iPre - 2, 2); + && iPre - 1 > 0 && m_code.at(iPre - 2) == QLatin1Char('/')) { + commentStartStr = m_code.mid(iPre - 2, 2); --iPre; } else { break; @@ -515,10 +523,10 @@ void AstComments::collectComments(std::shared_ptr<Engine> engine, AST::Node *n, 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 && code.at(i - 1) == QLatin1Char('\r')) + if (c == QLatin1Char('\n') && i > 0 && m_code.at(i - 1) == QLatin1Char('\r')) --i; - while (i > 0 && code.at(--i).isSpace()) { - c = code.at(i); + while (i > 0 && m_code.at(--i).isSpace()) { + c = m_code.at(i); if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { ++preNewline; break; @@ -530,194 +538,233 @@ void AstComments::collectComments(std::shared_ptr<Engine> engine, AST::Node *n, } if (iPre == 0) preNewline = 1; - qsizetype iPost = cLoc.end(); - while (iPost < code.size()) { - QChar c = code.at(iPost); + 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 < code.size() - && code.at(iPost + 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 (c == QLatin1Char('\n')) - break; - if (c == QLatin1Char('\r')) { - if (iPost < code.size() && code.at(iPost) == QLatin1Char('\n')) - ++iPost; + if (postNewline > 1) break; - } } - ElementRef commentEl; - bool pre = true; - auto iStart = ranges.starts.lowerBound(cLoc.begin()); - auto iEnd = ranges.ends.lowerBound(cLoc.begin()); - Q_ASSERT(!ranges.ends.isEmpty() && !ranges.starts.isEmpty()); - - auto checkElementBefore = [&]() { - if (commentEl) - return; - // prefer post comment attached to preceding element - auto preEnd = iEnd; - auto preStart = iStart; - if (preEnd != ranges.ends.begin()) { - --preEnd; - if (iStart == 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 = iPre; - while (i != 0 && code.at(--i).isSpace()) - ; - if (i <= preEnd.key() || i < lastPostCommentPostEnd - || iEnd == ranges.ends.end()) { - commentEl = preEnd.value(); - pre = false; - lastPostCommentPostEnd = iPost + 1; // ensure the previous check works - // with multiple post comments - } - } - } - }; - auto checkElementAfter = [&]() { - if (commentEl) - return; - if (iStart != ranges.starts.end()) { - // try to add a pre comment of following element - if (iEnd == ranges.ends.end() || iEnd.key() > iStart.key()) { - // there is no end of element before iStart begins - // associate the comment as preComment of iStart - // (btw iEnd == end should never happen here) - commentEl = iStart.value(); - return; + + 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 } } - if (iStart == ranges.starts.begin()) { - Q_ASSERT(iStart != ranges.starts.end()); - // we are before the first node (should be handled already by previous case) - commentEl = iStart.value(); - } - }; - auto checkInsideEl = [&]() { - if (commentEl) - return; - auto preIStart = iStart; - if (iStart == ranges.starts.begin()) { - commentEl = iStart.value(); // checkElementAfter should have handled this + } + } + // 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; - } else { - --preIStart; } - // 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 = preIStart.value(); - ElementRef n2 = iEnd.value(); - if (n1.size > n2.size) - commentEl = n2; - else - commentEl = n1; - }; - if (!preNewline) { - checkElementBefore(); - checkElementAfter(); - } else { - checkElementAfter(); - checkElementBefore(); } - if (!commentEl) - checkInsideEl(); - if (!commentEl) { - qCWarning(commentsLog) << "Could not assign comment at" << locationToData(cLoc) - << "adding before root node"; - if (rootItem && (rootItemLocations || !n)) { - commentEl.element = RegionRef { Path(), QString() }; - commentEl.size = - rootItemLocations->info() - .regions.value(QString(), rootItemLocations->info().fullRegion) - .length; - // attach to rootItem - } else if (n) { - commentEl.element = n; - commentEl.size = n->lastSourceLocation().end() - n->firstSourceLocation().begin(); - } + 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(); } - Comment comment(code.mid(iPre, iPost - iPre), preNewline); - if (commentEl.element.index() == 0 && std::get<0>(commentEl.element)) { - CommentedElement &cEl = commentedElements[std::get<0>(commentEl.element)]; - if (pre) - cEl.preComments.append(comment); - else - cEl.postComments.append(comment); - } else if (commentEl.element.index() == 1) { - DomItem rComments = rootItem.item() - .path(std::get<1>(commentEl.element).path) - .field(Fields::comments); - if (RegionComments *rCommentsPtr = rComments.mutableAs<RegionComments>()) { - if (pre) - rCommentsPtr->addPreComment(comment, std::get<1>(commentEl.element).regionName); - else - rCommentsPtr->addPostComment(comment, - std::get<1>(commentEl.element).regionName); - } else { - Q_ASSERT(false); - } + } + 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 { - qCWarning(commentsLog) - << "Failed: no item or node to attach comment" << comment.rawComment(); + --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; +}; -// internal class to collect all comments in a node or its subnodes -class CommentCollectorVisitor : protected VisitAll +/*! +\class QQmlJS::Dom::AstComments +\brief Stores the comments associated with javascript AST::Node pointers +*/ +bool AstComments::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { -public: - CommentCollectorVisitor(AstComments *comments, AST::Node *n) : comments(comments) - { - AST::Node::accept(n, this); + // 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); - void throwRecursionDepthError() override { } - - bool preVisit(Node *n) override - { - auto &cEls = comments->commentedElements(); - if (cEls.contains(n)) - nodeComments += cEls[n].commentGroups( - combine(n->firstSourceLocation(), n->lastSourceLocation())); - return true; - } + return false; +} - AstComments *comments; - QMultiMap<quint32, const QList<Comment> *> nodeComments; -}; +CommentCollector::CommentCollector(MutableDomItem item) + : m_rootItem{ std::move(item) }, + m_fileLocations{ FileLocations::treeOf(m_rootItem.item()) } +{ +} -/*! -\brief low level method returns all comments in a node (including its subnodes) +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(); + } +} -The comments are roughly ordered in the order they appear in the file. -Multiple values are in reverse order if the index is even. +/*! \internal + \brief Collects and associates comments with javascript AST::Node pointers + or with MutableDomItem */ -QMultiMap<quint32, const QList<Comment> *> AstComments::allCommentsInNode(AST::Node *n) +void CommentCollector::collectComments( + const std::shared_ptr<Engine> &engine, AST::Node *rootNode, + const std::shared_ptr<AstComments> &astComments) { - CommentCollectorVisitor v(this, n); - return v.nodeComments; + 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(DomItem &self, DirectVisitor visitor) +bool RegionComments::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; - if (!regionComments.isEmpty()) - cont = cont && self.dvWrapField(visitor, Fields::regionComments, regionComments); + 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; } diff --git a/src/qmldom/qqmldomcomments_p.h b/src/qmldom/qqmldomcomments_p.h index e4f85fa3e3..f8fe46c098 100644 --- a/src/qmldom/qqmldomcomments_p.h +++ b/src/qmldom/qqmldomcomments_p.h @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// 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 @@ -17,7 +17,6 @@ #include "qqmldom_fwd_p.h" #include "qqmldomconstants_p.h" -#include "qqmldomfunctionref_p.h" #include "qqmldomitem_p.h" #include "qqmldomattachedinfo_p.h" @@ -39,7 +38,7 @@ class QMLDOM_EXPORT CommentInfo { Q_DECLARE_TR_FUNCTIONS(CommentInfo) public: - CommentInfo(QStringView); + CommentInfo(QStringView, QQmlJS::SourceLocation loc); QStringView preWhitespace() const { return rawComment.mid(0, commentBegin); } @@ -55,17 +54,22 @@ public: return rawComment.mid(commentEnd, rawComment.size() - commentEnd); } - quint32 commentBegin; - quint32 commentEnd; - quint32 commentContentBegin; - quint32 commentContentEnd; + // Comment source location populated during lexing doesn't include start strings // or /* + // Returns the location starting from // or /* + QQmlJS::SourceLocation sourceLocation() const { return commentLocation; } + + quint32 commentBegin = 0; + quint32 commentEnd = 0; + quint32 commentContentBegin = 0; + quint32 commentContentEnd = 0; QStringView commentStartStr; QStringView commentEndStr; bool hasStartNewline = false; bool hasEndNewline = false; - int nContentNewlines; + int nContentNewlines = 0; QStringView rawComment; QStringList warnings; + QQmlJS::SourceLocation commentLocation; }; class QMLDOM_EXPORT Comment @@ -74,21 +78,28 @@ public: constexpr static DomType kindValue = DomType::Comment; DomType kind() const { return kindValue; } - Comment(QString c, int newlinesBefore = 1) - : m_commentStr(c), m_comment(m_commentStr), m_newlinesBefore(newlinesBefore) + 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, int newlinesBefore = 1) : m_comment(c), m_newlinesBefore(newlinesBefore) + 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(DomItem &self, DirectVisitor visitor); + 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); } + CommentInfo info() const { return CommentInfo(m_comment, m_location); } 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; @@ -96,9 +107,10 @@ public: friend bool operator!=(const Comment &c1, const Comment &c2) { return !(c1 == c2); } private: - QString m_commentStr; QStringView m_comment; + QQmlJS::SourceLocation m_location; int m_newlinesBefore; + CommentType m_type; }; class QMLDOM_EXPORT CommentedElement @@ -107,22 +119,33 @@ public: constexpr static DomType kindValue = DomType::CommentedElement; DomType kind() const { return kindValue; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + 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; - QMultiMap<quint32, const QList<Comment> *> commentGroups(SourceLocation elLocation) const; friend bool operator==(const CommentedElement &c1, const CommentedElement &c2) { - return c1.preComments == c2.preComments && c1.postComments == c2.postComments; + 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); } - QList<Comment> preComments; - QList<Comment> postComments; + 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 @@ -131,46 +154,56 @@ public: constexpr static DomType kindValue = DomType::RegionComments; DomType kind() const { return kindValue; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; friend bool operator==(const RegionComments &c1, const RegionComments &c2) { - return c1.regionComments == c2.regionComments; + return c1.m_regionComments == c2.m_regionComments; } friend bool operator!=(const RegionComments &c1, const RegionComments &c2) { return !(c1 == c2); } - Path addPreComment(const Comment &comment, QString regionName) + 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 = regionComments[regionName].preComments; + auto &preList = m_regionComments[region].preComments(); index_type idx = preList.size(); - preList.append(comment); + m_regionComments[region].addComment(comment); return Path::Field(Fields::regionComments) - .key(regionName) + .key(fileLocationRegionName(region)) .field(Fields::preComments) .index(idx); } - Path addPostComment(const Comment &comment, QString regionName) + Path addPostComment(const Comment &comment, FileLocationRegion region) { - auto &postList = regionComments[regionName].postComments; + auto &postList = m_regionComments[region].postComments(); index_type idx = postList.size(); - postList.append(comment); + m_regionComments[region].addComment(comment); return Path::Field(Fields::regionComments) - .key(regionName) + .key(fileLocationRegionName(region)) .field(Fields::postComments) .index(idx); } - QMap<QString, CommentedElement> regionComments; + QMap<FileLocationRegion, CommentedElement> m_regionComments; }; class QMLDOM_EXPORT AstComments final : public OwningItem { protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { return std::make_shared<AstComments>(*this); } @@ -178,18 +211,14 @@ protected: public: constexpr static DomType kindValue = DomType::AstComments; DomType kind() const override { return kindValue; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - std::shared_ptr<AstComments> makeCopy(DomItem &self) const + 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(DomItem &self) const override { return self.m_ownerPath; } - static void collectComments(MutableDomItem &item); - static void collectComments(std::shared_ptr<Engine> engine, AST::Node *n, - std::shared_ptr<AstComments> collectComments, - MutableDomItem rootItem, FileLocations::Tree rootItemLocations); - AstComments(std::shared_ptr<Engine> e) : m_engine(e) { } + 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) { @@ -199,6 +228,12 @@ public: { return m_commentedElements; } + + QHash<AST::Node *, CommentedElement> &commentedElements() + { + return m_commentedElements; + } + CommentedElement *commentForNode(AST::Node *n) { if (m_commentedElements.contains(n)) @@ -212,6 +247,20 @@ private: 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: diff --git a/src/qmldom/qqmldomcompare.cpp b/src/qmldom/qqmldomcompare.cpp index 92e18944c0..65b357cc75 100644 --- a/src/qmldom/qqmldomcompare.cpp +++ b/src/qmldom/qqmldomcompare.cpp @@ -8,8 +8,8 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -bool domCompare(DomItem &i1, DomItem &i2, function_ref<bool(Path, DomItem &, DomItem &)> change, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter, +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(); @@ -144,21 +144,26 @@ bool domCompare(DomItem &i1, DomItem &i2, function_ref<bool(Path, DomItem &, Dom if (v1 != v2) return change(basePath, i1, i2); } break; + case DomKind::ScriptElement: { + // TODO: implement me + return false; + + } break; } } return true; } QStringList -domCompareStrList(DomItem &i1, DomItem &i2, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter, +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](Path p, DomItem &j1, DomItem &j2) { + [&res, &hasDiff, stopAtFirstDiff](const Path &p, const DomItem &j1, const DomItem &j2) { hasDiff = true; if (!j1) { res.append(QStringLiteral("- %1\n").arg(p.toString())); @@ -206,6 +211,10 @@ domCompareStrList(DomItem &i1, DomItem &i2, .arg(j2.toString())); } } break; + case DomKind::ScriptElement: { + // implement me + break; + } } } } diff --git a/src/qmldom/qqmldomcompare_p.h b/src/qmldom/qqmldomcompare_p.h index b15fd562fe..651486d7a9 100644 --- a/src/qmldom/qqmldomcompare_p.h +++ b/src/qmldom/qqmldomcompare_p.h @@ -26,20 +26,20 @@ namespace QQmlJS { namespace Dom { bool domCompare( - DomItem &i1, DomItem &i2, function_ref<bool(Path, DomItem &, DomItem &)> change, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, + 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( - DomItem &i1, DomItem &i2, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, + 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, DomItem &i2, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, + 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(); @@ -47,8 +47,8 @@ inline QStringList domCompareStrList( } inline QStringList domCompareStrList( - DomItem &i1, MutableDomItem &i2, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, + 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(); @@ -57,7 +57,7 @@ inline QStringList domCompareStrList( inline QStringList domCompareStrList( MutableDomItem &i1, MutableDomItem &i2, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter, DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff) { DomItem ii1 = i1.item(); diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h index a648379551..ac5f8f67c4 100644 --- a/src/qmldom/qqmldomconstants_p.h +++ b/src/qmldom/qqmldomconstants_p.h @@ -101,13 +101,7 @@ Q_ENUM_NS(VisitPrototypesOption) Q_DECLARE_FLAGS(VisitPrototypesOptions, VisitPrototypesOption) Q_DECLARE_OPERATORS_FOR_FLAGS(VisitPrototypesOptions) -enum class DomKind { - Empty, - Object, - List, - Map, - Value -}; +enum class DomKind { Empty, Object, List, Map, Value, ScriptElement }; Q_ENUM_NS(DomKind) enum class DomType { @@ -130,7 +124,8 @@ enum class DomType { // types 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 + 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 @@ -150,23 +145,29 @@ enum class DomType { 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 + 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, // 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 + 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 + 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 // 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 + // Moc objects, mainly for testing ### Try to remove them; replace their usage in tests with + // "real" instances MockObject, MockOwner, @@ -182,7 +183,54 @@ enum class DomType { // Dom top level 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 + 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, + ScriptFunctionExpression, + ScriptYieldExpression, + + ScriptElementStop, // marker to check if a DomType is a scriptelement or not }; Q_ENUM_NS(DomType) @@ -203,14 +251,6 @@ enum class ListOptions { }; Q_ENUM_NS(ListOptions) -enum class LoadOption { - DefaultLoad = 0x0, - ForceLoad = 0x1, -}; -Q_ENUM_NS(LoadOption) -Q_DECLARE_FLAGS(LoadOptions, LoadOption) -Q_DECLARE_OPERATORS_FOR_FLAGS(LoadOptions) - enum class EscapeOptions{ OuterQuotes, NoOuterQuotes @@ -246,6 +286,14 @@ 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) @@ -266,22 +314,101 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(WriteOutChecks) enum class LocalSymbolsType { None = 0x0, - QmlTypes = 0x1, - Types = 0x3, - Signals = 0x4, - Methods = 0xC, - Attributes = 0x10, - Ids = 0x20, - Components = 0x40, - Namespaces = 0x80, - Globals = 0x100, - MethodParameters = 0x200, - All = 0x3FF + 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, + IfKeywordRegion, + ImportTokenRegion, + ImportUriRegion, + InOfTokenRegion, + LeftBraceRegion, + LeftBracketRegion, + LeftParenthesisRegion, + MainRegion, + OperatorTokenRegion, + OnTargetRegion, + OnTokenRegion, + PragmaKeywordRegion, + PragmaValuesRegion, + PropertyKeywordRegion, + QuestionMarkTokenRegion, + ReadonlyKeywordRegion, + RequiredKeywordRegion, + ReturnKeywordRegion, + RightBraceRegion, + RightBracketRegion, + RightParenthesisRegion, + SecondSemicolonRegion, + SemicolonTokenRegion, + SignalKeywordRegion, + SwitchKeywordRegion, + ThrowKeywordRegion, + TryKeywordRegion, + TypeIdentifierRegion, + VersionRegion, + WhileKeywordRegion, + YieldKeywordRegion, +}; +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 index 74963aef3b..348984172f 100644 --- a/src/qmldom/qqmldomelements.cpp +++ b/src/qmldom/qqmldomelements.cpp @@ -5,6 +5,8 @@ // 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 @@ -43,7 +45,7 @@ namespace Dom { namespace Paths { -Path moduleIndexPath(QString uri, int majorVersion, ErrorHandler errorHandler) +Path moduleIndexPath(const QString &uri, int majorVersion, const ErrorHandler &errorHandler) { QString version = QString::number(majorVersion); if (majorVersion == Version::Latest) @@ -59,7 +61,7 @@ Path moduleIndexPath(QString uri, int majorVersion, ErrorHandler errorHandler) return Path::Root(PathRoot::Env).field(Fields::moduleIndexWithUri).key(uri).key(version); } -Path moduleScopePath(QString uri, Version version, ErrorHandler) +Path moduleScopePath(const QString &uri, Version version, const ErrorHandler &) { return Path::Root(PathRoot::Env) .field(Fields::moduleIndexWithUri) @@ -69,7 +71,7 @@ Path moduleScopePath(QString uri, Version version, ErrorHandler) .key(version.minorString()); } -Path moduleScopePath(QString uri, QString version, ErrorHandler errorHandler) +Path moduleScopePath(const QString &uri, const QString &version, const ErrorHandler &errorHandler) { Version v = Version::fromString(version); if (!version.isEmpty() && !(v.isValid() || v.isLatest())) @@ -85,25 +87,25 @@ static ErrorGroups domParsingErrors() return res; } -bool CommentableDomElement::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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(Path newPath) +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(QString name) : CommentableDomElement(Path()), m_name(name) { } +Component::Component(const QString &name) : CommentableDomElement(Path()), m_name(name) { } -Component::Component(Path pathFromOwner) : CommentableDomElement(pathFromOwner) { } +Component::Component(const Path &pathFromOwner) : CommentableDomElement(pathFromOwner) { } -bool Component::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Component::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::name, name()); @@ -117,20 +119,13 @@ bool Component::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -DomItem Component::field(DomItem &self, QStringView name) +DomItem Component::field(const DomItem &self, QStringView name) const { - switch (name.size()) { - case 4: - if (name == Fields::name) - return self.wrapField(Fields::name, m_name); - break; - case 7: - if (name == Fields::objects) - return self.wrapField(Fields::objects, m_objects); - break; - default: - break; - } + 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); } @@ -140,45 +135,50 @@ Path Component::addObject(const QmlObject &object, QmlObject **oPtr) oPtr); } -bool QmlComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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(Path newPath) +void QmlComponent::updatePathFromOwner(const Path &newPath) { Component::updatePathFromOwner(newPath); updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations)); } -void QmlComponent::writeOut(DomItem &self, OutWriter &lw) const +void QmlComponent::writeOut(const DomItem &self, OutWriter &lw) const { if (name().contains(QLatin1Char('.'))) { // inline component lw.ensureNewline() - .writeRegion(u"component") + .writeRegion(ComponentKeywordRegion) .space() - .writeRegion(u"componentName", name().split(QLatin1Char('.')).last()) - .writeRegion(u"colon", u":") + .writeRegion(IdentifierRegion, name().split(QLatin1Char('.')).last()) + .writeRegion(ColonTokenRegion) .space(); } self.field(Fields::objects).index(0).writeOut(lw); } -QList<QString> QmlComponent::subComponentsNames(DomItem &self) const +QList<QString> QmlComponent::subComponentsNames(const DomItem &self) const { DomItem components = self.owner().field(Fields::components); - QSet<QString> cNames = components.keys(); + const QSet<QString> cNames = components.keys(); QString myNameDot = self.pathFromOwner()[1].headName(); if (!myNameDot.isEmpty()) myNameDot += QLatin1Char('.'); QList<QString> subNames; - for (QString cName : cNames) + for (const QString &cName : cNames) if (cName.startsWith(myNameDot) && !QStringView(cName).mid(myNameDot.size()).contains(QLatin1Char('.')) && !cName.isEmpty()) @@ -187,12 +187,12 @@ QList<QString> QmlComponent::subComponentsNames(DomItem &self) const return subNames; } -QList<DomItem> QmlComponent::subComponents(DomItem &self) const +QList<DomItem> QmlComponent::subComponents(const DomItem &self) const { DomItem components = self.owner().field(Fields::components); QList<DomItem> res; - for (QString cName : subComponentsNames(self)) - for (DomItem comp : components.key(cName).values()) + for (const QString &cName : subComponentsNames(self)) + for (const DomItem &comp : components.key(cName).values()) res.append(comp); return res; } @@ -244,7 +244,7 @@ QString Version::stringValue() const return QString::number(majorVersion) + QChar::fromLatin1('.') + QString::number(minorVersion); } -bool Version::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Version::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvWrapField(visitor, Fields::majorVersion, majorVersion); @@ -264,7 +264,8 @@ QRegularExpression Import::importRe() return res; } -Import Import::fromUriString(QString importStr, Version v, QString importId, ErrorHandler handler) +Import Import::fromUriString( + const QString &importStr, Version v, const QString &importId, const ErrorHandler &handler) { auto m = importRe().match(importStr); if (m.hasMatch()) { @@ -276,15 +277,21 @@ Import Import::fromUriString(QString importStr, Version v, QString importId, Err "version %3") .arg(m.captured(2), importStr, v.stringValue())) .handle(handler); - if (importId.isEmpty()) - importId = 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); - return Import(QmlUri::fromUriString(m.captured(u"uri").trimmed()), v, importId); + 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)) @@ -292,12 +299,13 @@ Import Import::fromUriString(QString importStr, Version v, QString importId, Err return Import(); } -Import Import::fromFileString(QString importStr, QString importId, ErrorHandler) +Import Import::fromFileString( + const QString &importStr, const QString &importId, const ErrorHandler &) { return Import(QmlUri::fromDirectoryString(importStr), Version(), importId); } -bool Import::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Import::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::uri, uri.toString()); @@ -310,48 +318,74 @@ bool Import::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -void Import::writeOut(DomItem &, OutWriter &ow) const +void Import::writeOut(const DomItem &self, OutWriter &ow) const { if (implicit) return; - ow.ensureNewline(); - ow.writeRegion(u"import").space(); - ow.writeRegion(u"uri", uri.toString()); + + 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(u"as").space().writeRegion(u"id", importId); + ow.space().writeRegion(AsTokenRegion).space().writeRegion(IdNameRegion, importId); } -Id::Id(QString idName, Path referredObject) : name(idName), referredObjectPath(referredObject) { } +Id::Id(const QString &idName, const Path &referredObject) : name(idName), referredObjectPath(referredObject) { } -bool Id::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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(Path newPath) +void Id::updatePathFromOwner(const Path &newPath) { updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); } -Path Id::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +Path Id::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) { return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, annotation, aPtr); } -QmlObject::QmlObject(Path pathFromOwner) : CommentableDomElement(pathFromOwner) { } +QmlObject::QmlObject(const Path &pathFromOwner) : CommentableDomElement(pathFromOwner) { } -bool QmlObject::iterateBaseDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool QmlObject::iterateBaseDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); if (!idStr().isEmpty()) @@ -369,13 +403,18 @@ bool QmlObject::iterateBaseDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::propertyInfos, [this, &self]() { return self.subMapItem(Map( pathFromOwner().field(Fields::propertyInfos), - [&self](DomItem &map, QString k) { + [&self](const DomItem &map, const QString &k) { auto pInfo = self.propertyInfoWithName(k); return map.wrap(PathEls::Key(k), pInfo); }, - [&self](DomItem &) { return self.propertyInfoNames(); }, + [&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; } @@ -391,7 +430,7 @@ QList<QString> QmlObject::fields() const return myFields; } -bool QmlObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool QmlObject::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = iterateBaseDirectSubpaths(self, visitor); cont = cont && self.dvValueLazyField(visitor, Fields::defaultPropertyName, [this, &self]() { @@ -400,91 +439,65 @@ bool QmlObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -DomItem QmlObject::field(DomItem &self, QStringView name) +DomItem QmlObject::field(const DomItem &self, QStringView name) const { - switch (name.size()) { - case 4: - if (name == Fields::name) - return self.subDataItem(PathEls::Field(Fields::name), this->name()); - break; - case 5: - if (name == Fields::idStr) { - if (idStr().isEmpty()) - return DomItem(); - return self.subDataItem(PathEls::Field(Fields::idStr), idStr()); - } - break; - case 7: - if (name == Fields::methods) - return self.wrapField(Fields::methods, m_methods); - break; - case 8: - switch (name.at(1).unicode()) { - case u'i': - if (name == Fields::bindings) - return self.wrapField(Fields::bindings, m_bindings); - break; - case u'o': - if (name == Fields::comments) - return CommentableDomElement::field(self, name); - break; - case u'h': - if (name == Fields::children) - return self.wrapField(Fields::children, m_children); - break; - default: - break; - } - break; - case 9: - if (name == Fields::nextScope) { - if (nextScopePath()) - return self.subReferenceItem(PathEls::Field(Fields::nextScope), nextScopePath()); - else - return DomItem(); - } - break; - case 10: - if (name == Fields::prototypes) { - if (prototypePaths().isEmpty()) - return DomItem(); - return self.subReferencesItem(PathEls::Field(Fields::prototypes), m_prototypePaths); - } - break; - case 11: - if (name == Fields::annotations) - return self.wrapField(Fields::annotations, m_annotations); - break; - case 12: + 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); - break; - case 13: - if (name == Fields::propertyInfos) - return self.subMapItem(Map( - pathFromOwner().field(Fields::propertyInfos), - [self](DomItem &map, QString k) mutable { - auto pInfo = self.propertyInfoWithName(k); - return map.wrap(PathEls::Key(k), pInfo); - }, - [self](DomItem &) mutable { return self.propertyInfoNames(); }, - QLatin1String("PropertyInfo"))); - break; - case 19: - if (name == Fields::defaultPropertyName) - return self.subDataItem(PathEls::Field(Fields::defaultPropertyName), - defaultPropertyName(self)); - break; - default: - break; + 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)) + if (!knownLookups.contains(name)) { qCWarning(domLog()) << "Asked non existing field " << name << " in QmlObject " << pathFromOwner(); + } return DomItem(); } -void QmlObject::updatePathFromOwner(Path newPath) +void QmlObject::updatePathFromOwner(const Path &newPath) { DomElement::updatePathFromOwner(newPath); updatePathFromOwnerMultiMap(m_propertyDefs, newPath.field(Fields::propertyDefs)); @@ -504,14 +517,14 @@ QString QmlObject::localDefaultPropertyName() const return QString(); } -QString QmlObject::defaultPropertyName(DomItem &self) const +QString QmlObject::defaultPropertyName(const DomItem &self) const { QString dProp = localDefaultPropertyName(); if (!dProp.isEmpty()) return dProp; QString res = QStringLiteral(u"data"); self.visitPrototypeChain( - [&res](DomItem &obj) { + [&res](const DomItem &obj) { if (const QmlObject *objPtr = obj.as<QmlObject>()) { QString dProp = objPtr->localDefaultPropertyName(); if (!dProp.isEmpty()) { @@ -525,10 +538,10 @@ QString QmlObject::defaultPropertyName(DomItem &self) const return res; } -bool QmlObject::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &)> visitor) const +bool QmlObject::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &)> visitor) const { - bool cont = self.field(Fields::bindings).visitKeys([visitor](QString, DomItem &bs) { - return bs.visitIndexes([visitor](DomItem &b) { + 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)) @@ -538,7 +551,7 @@ bool QmlObject::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &)> vi return true; }); }); - cont = cont && self.field(Fields::children).visitIndexes([visitor](DomItem &qmlObj) { + cont = cont && self.field(Fields::children).visitIndexes([visitor](const DomItem &qmlObj) { if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) { return qmlObjPtr->iterateSubOwners(qmlObj, visitor); } @@ -548,7 +561,7 @@ bool QmlObject::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &)> vi return cont; } -static QStringList dotExpressionToList(std::shared_ptr<ScriptExpression> expr) +static QStringList dotExpressionToList(const std::shared_ptr<ScriptExpression> &expr) { QStringList res; AST::Node *node = (expr ? expr->ast() : nullptr); @@ -574,14 +587,14 @@ static QStringList dotExpressionToList(std::shared_ptr<ScriptExpression> expr) return res; } -LocallyResolvedAlias QmlObject::resolveAlias(DomItem &self, +LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self, std::shared_ptr<ScriptExpression> accessSequence) const { QStringList accessSequenceList = dotExpressionToList(accessSequence); return resolveAlias(self, accessSequenceList); } -LocallyResolvedAlias QmlObject::resolveAlias(DomItem &self, const QStringList &accessSequence) const +LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self, const QStringList &accessSequence) const { LocallyResolvedAlias res; QSet<QString> visitedAlias; @@ -669,8 +682,8 @@ LocallyResolvedAlias QmlObject::resolveAlias(DomItem &self, const QStringList &a return res; } -MutableDomItem QmlObject::addPropertyDef(MutableDomItem &self, PropertyDefinition propertyDef, - AddOption option) +MutableDomItem QmlObject::addPropertyDef( + MutableDomItem &self, const PropertyDefinition &propertyDef, AddOption option) { Path p = addPropertyDef(propertyDef, option); if (p.last().headIndex(0) > 1) @@ -688,7 +701,8 @@ MutableDomItem QmlObject::addBinding(MutableDomItem &self, Binding binding, AddO return self.owner().path(p); } -MutableDomItem QmlObject::addMethod(MutableDomItem &self, MethodInfo functionDef, AddOption option) +MutableDomItem QmlObject::addMethod( + MutableDomItem &self, const MethodInfo &functionDef, AddOption option) { Path p = addMethod(functionDef, option); if (p.last().headIndex(0) > 1) @@ -697,7 +711,7 @@ MutableDomItem QmlObject::addMethod(MutableDomItem &self, MethodInfo functionDef return self.owner().path(p); } -void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const +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 @@ -707,10 +721,10 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const DomItem owner = self.owner(); if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>()) code = qmlFilePtr->code(); - ow.writeRegion(u"name", name()); + ow.writeRegion(IdentifierRegion, name()); if (!onTarget.isEmpty()) - ow.space().writeRegion(u"on", u"on").space().writeRegion(u"onTarget", onTarget).space(); - ow.writeRegion(u"leftBrace", u" {").newline(); + 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 @@ -718,22 +732,24 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const if (myId) myId.writeOutPre(ow); ow.ensureNewline() - .writeRegion(u"idToken", u"id") - .writeRegion(u"idColon", u":") + .writeRegion(IdTokenRegion) + .writeRegion(IdColonTokenRegion) .space() - .writeRegion(u"id", idStr()); + .writeRegion(IdNameRegion, idStr()); if (ow.lineWriter.options().attributesSequence == LineWriterOptions::AttributesSequence::Normalize) { ow.ensureNewline(2); } - if (myId) + if (myId) { myId.writeOutPost(ow); + ow.ensureNewline(1); + } } quint32 counter = ow.counter(); DomItem component; if (isRootObject) component = self.containingObject(); - auto startLoc = [&](FileLocations::Tree l) { + auto startLoc = [&](const FileLocations::Tree &l) { if (l) return l->info().fullRegion; return SourceLocation(posOfNewElements, 0, 0, 0); @@ -746,13 +762,16 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const FileLocations::Tree componentLoc; if (isRootObject && objLoc.foundTree) componentLoc = objLoc.foundTree->parent()->parent(); - auto addMMap = [&attribs, &startLoc](DomItem &base, FileLocations::Tree baseLoc) { + auto addMMap + = [&attribs, &startLoc](const DomItem &base, const FileLocations::Tree &baseLoc) { if (!base) return; - for (auto els : base.values()) { + const auto values = base.values(); + for (const auto &els : values) { FileLocations::Tree elsLoc = FileLocations::find(baseLoc, els.pathFromOwner().last()); - for (auto el : els.values()) { + 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)); @@ -763,10 +782,12 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const DomItem base = this->field(self, fieldName); addMMap(base, FileLocations::find(objLoc.foundTree, base.pathFromOwner().last())); }; - auto addSingleLevel = [&attribs, &startLoc](DomItem &base, FileLocations::Tree baseLoc) { + auto addSingleLevel + = [&attribs, &startLoc](const DomItem &base, const FileLocations::Tree &baseLoc) { if (!base) return; - for (auto el : base.values()) { + 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)); } @@ -783,7 +804,7 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const FileLocations::find(objLoc.foundTree, children.pathFromOwner().last())); if (isRootObject) { DomItem subCs = component.field(Fields::subComponents); - for (DomItem &c : subCs.values()) { + for (const DomItem &c : subCs.values()) { AttachedInfoLookupResult<FileLocations::Tree> subLoc = FileLocations::findAttachedInfo(c); Q_ASSERT(subLoc.foundTree); @@ -839,16 +860,17 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const else { qWarning() << "Internal error casting binding to Binding in" << b.canonicalPath(); - ow.writeRegion(u"leftBrace", u"{").writeRegion(u"rightBrace", u"}"); + ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion); } b.writeOutPost(ow); } } else { el.second.writeOut(ow); } + ow.ensureNewline(); } ow.decreaseIndent(1, baseIndent); - ow.ensureNewline().write(u"}"); + ow.writeRegion(RightBraceRegion); return; } @@ -856,8 +878,10 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const DomItem propertyDefs = field(self, Fields::propertyDefs); if (isRootObject) { - for (auto enumDescs : component.field(Fields::enumerations).values()) { - for (auto enumDesc : enumDescs.values()) { + 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); @@ -868,14 +892,14 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const spacerId = ow.addNewlinesAutospacerCallback(2); QSet<QString> mergedDefBinding; for (const QString &defName : propertyDefs.sortedKeys()) { - auto pDefs = propertyDefs.key(defName).values(); - for (auto pDef : pDefs) { + 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](DomItem &el) { + 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()) { @@ -911,7 +935,7 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const else { qWarning() << "Internal error casting binding to Binding in" << b.canonicalPath(); - ow.writeRegion(u"leftBrace", u"{").writeRegion(u"rightBrace", u"}"); + ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion); } b.writeOutPost(ow); } @@ -919,8 +943,10 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const } ow.removeTextAddCallback(spacerId); QList<DomItem> signalList, methodList; - for (auto ms : field(self, Fields::methods).values()) { - for (auto m : ms.values()) { + 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); @@ -930,7 +956,7 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const } if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); - for (auto &sig : signalList) { + for (const auto &sig : std::as_const(signalList)) { ow.ensureNewline(); sig.writeOut(ow); ow.ensureNewline(); @@ -939,7 +965,7 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); bool first = true; - for (auto &method : methodList) { + for (const auto &method : std::as_const(methodList)) { if (!first && ow.lineWriter.options().functionsSpacing) { ow.newline(); } @@ -950,9 +976,10 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const } ow.removeTextAddCallback(spacerId); QList<DomItem> normalBindings, signalHandlers, delayedBindings; - for (auto bName : bindings.sortedKeys()) { + for (const auto &bName : bindings.sortedKeys()) { bool skipFirstNormal = mergedDefBinding.contains(bName); - for (auto b : bindings.key(bName).values()) { + 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) { @@ -971,23 +998,24 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const } if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); - for (auto &b : normalBindings) + for (const auto &b : std::as_const(normalBindings)) b.writeOut(ow); ow.removeTextAddCallback(spacerId); if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); - for (auto &b : delayedBindings) + for (const auto &b : std::as_const(delayedBindings)) b.writeOut(ow); ow.removeTextAddCallback(spacerId); if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); - for (auto &b : signalHandlers) + for (const auto &b : std::as_const(signalHandlers)) b.writeOut(ow); ow.removeTextAddCallback(spacerId); if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); first = true; - for (auto c : field(self, Fields::children).values()) { + const auto values = field(self, Fields::children).values(); + for (const auto &c : values) { if (!first && ow.lineWriter.options().objectsSpacing) { ow.newline().newline(); } @@ -1001,27 +1029,30 @@ void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const DomItem subComps = component.field(Fields::subComponents); if (counter != ow.counter()) spacerId = ow.addNewlinesAutospacerCallback(2); - for (auto subC : subComps.values()) { + const auto values = subComps.values(); + for (const auto &subC : values) { ow.ensureNewline(); subC.writeOut(ow); } ow.removeTextAddCallback(spacerId); } ow.decreaseIndent(1, baseIndent); - ow.ensureNewline().write(u"}"); + ow.ensureNewline().writeRegion(RightBraceRegion); } -Binding::Binding(QString name, std::unique_ptr<BindingValue> value, BindingType bindingType) +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(QString name, std::shared_ptr<ScriptExpression> value, BindingType bindingType) +Binding::Binding( + const QString &name, const std::shared_ptr<ScriptExpression> &value, + BindingType bindingType) : Binding(name, std::make_unique<BindingValue>(value), bindingType) { } -Binding::Binding(QString name, QString scriptCode, BindingType 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, @@ -1030,12 +1061,12 @@ Binding::Binding(QString name, QString scriptCode, BindingType bindingType) { } -Binding::Binding(QString name, QmlObject value, BindingType bindingType) +Binding::Binding(const QString &name, const QmlObject &value, BindingType bindingType) : Binding(name, std::make_unique<BindingValue>(value), bindingType) { } -Binding::Binding(QString name, QList<QmlObject> value, BindingType bindingType) +Binding::Binding(const QString &name, const QList<QmlObject> &value, BindingType bindingType) : Binding(name, std::make_unique<BindingValue>(value), bindingType) { } @@ -1044,7 +1075,8 @@ 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_comments(o.m_comments), + m_bindingIdentifiers(o.m_bindingIdentifiers) { if (o.m_value) { m_value = std::make_unique<BindingValue>(*o.m_value); @@ -1059,6 +1091,7 @@ Binding &Binding::operator=(const Binding &o) 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); @@ -1070,7 +1103,7 @@ Binding &Binding::operator=(const Binding &o) return *this; } -bool Binding::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Binding::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::name, m_name); @@ -1089,11 +1122,16 @@ bool Binding::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) 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(DomItem &self) const +DomItem Binding::valueItem(const DomItem &self) const { if (!m_value) return DomItem(); @@ -1149,13 +1187,13 @@ std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() return nullptr; } -Path Binding::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +Path Binding::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) { return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), m_annotations, annotation, aPtr); } -void Binding::updatePathFromOwner(Path newPath) +void Binding::updatePathFromOwner(const Path &newPath) { Path base = newPath.field(Fields::annotations); if (m_value) @@ -1163,12 +1201,12 @@ void Binding::updatePathFromOwner(Path newPath) updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); } -void Binding::writeOut(DomItem &self, OutWriter &lw) const +void Binding::writeOut(const DomItem &self, OutWriter &lw) const { lw.ensureNewline(); if (m_bindingType == BindingType::Normal) { - lw.writeRegion(u"name", name()); - lw.writeRegion(u"colon", u":").space(); + lw.writeRegion(IdentifierRegion, name()); + lw.writeRegion(ColonTokenRegion).space(); writeOutValue(self, lw); } else { DomItem v = valueItem(self); @@ -1183,7 +1221,7 @@ void Binding::writeOut(DomItem &self, OutWriter &lw) const } } -void Binding::writeOutValue(DomItem &self, OutWriter &lw) const +void Binding::writeOutValue(const DomItem &self, OutWriter &lw) const { DomItem v = valueItem(self); switch (valueKind()) { @@ -1205,7 +1243,7 @@ void Binding::writeOutValue(DomItem &self, OutWriter &lw) const } } -bool QmltypesComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool QmltypesComponent::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = Component::iterateDirectSubpaths(self, visitor); cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); @@ -1220,7 +1258,8 @@ bool QmltypesComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visit return cont; } -Export Export::fromString(Path source, QStringView exp, Path typePath, ErrorHandler h) +Export Export::fromString( + const Path &source, QStringView exp, const Path &typePath, const ErrorHandler &h) { Export res; res.exportSourcePath = source; @@ -1237,14 +1276,13 @@ Export Export::fromString(Path source, QStringView exp, Path typePath, ErrorHand "or 'Name major.minor' not '%1'.") .arg(exp)) .handle(h); - QString package; if (slashIdx != -1) res.uri = exp.left(slashIdx).toString(); res.typeName = exp.mid(slashIdx + 1, spaceIdx - (slashIdx + 1)).toString(); return res; } -bool AttributeInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool AttributeInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::name, name); @@ -1257,20 +1295,20 @@ bool AttributeInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -Path AttributeInfo::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, +Path AttributeInfo::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) { return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, annotation, aPtr); } -void AttributeInfo::updatePathFromOwner(Path newPath) +void AttributeInfo::updatePathFromOwner(const Path &newPath) { Path base = newPath.field(Fields::annotations); updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); } -bool EnumDecl::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool EnumDecl::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::name, name()); @@ -1279,13 +1317,13 @@ bool EnumDecl::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -void EnumDecl::updatePathFromOwner(Path newPath) +void EnumDecl::updatePathFromOwner(const Path &newPath) { DomElement::updatePathFromOwner(newPath); updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); } -void EnumDecl::setAnnotations(QList<QmlObject> annotations) +void EnumDecl::setAnnotations(const QList<QmlObject> &annotations) { m_annotations = annotations; } @@ -1296,23 +1334,24 @@ Path EnumDecl::addAnnotation(const QmlObject &annotation, QmlObject **aPtr) annotation, aPtr); } -void EnumDecl::writeOut(DomItem &self, OutWriter &ow) const +void EnumDecl::writeOut(const DomItem &self, OutWriter &ow) const { - ow.writeRegion(u"enum", u"enum") + ow.writeRegion(EnumKeywordRegion) .space() - .writeRegion(u"name", name()) + .writeRegion(IdentifierRegion, name()) .space() - .writeRegion(u"lbrace", u"{"); + .writeRegion(LeftBraceRegion); int iLevel = ow.increaseIndent(1); - for (auto value : self.field(Fields::values).values()) { + 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(u"rbrace", u"}"); + ow.ensureNewline().writeRegion(RightBraceRegion); } -QList<Path> ImportScope::allSources(DomItem &self) const +QList<Path> ImportScope::allSources(const DomItem &self) const { DomItem top = self.top(); DomItem env = top.environment(); @@ -1330,7 +1369,7 @@ QList<Path> ImportScope::allSources(DomItem &self) const knownPaths.insert(pNow); res.append(pNow); DomItem sourceBase = top.path(pNow); - for (DomItem autoExp : sourceBase.field(Fields::autoExports).values()) { + for (const DomItem &autoExp : sourceBase.field(Fields::autoExports).values()) { if (const ModuleAutoExport *autoExpPtr = autoExp.as<ModuleAutoExport>()) { Path newSource; if (autoExpPtr->inheritVersion) { @@ -1364,14 +1403,14 @@ QList<Path> ImportScope::allSources(DomItem &self) const return res; } -bool ImportScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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), - [](DomItem &list, const PathEls::PathComponent &p, const Path &el) { + [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) { return list.subDataItem(p, el.toString()); })); }); @@ -1379,20 +1418,20 @@ bool ImportScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::imported, [this, &self]() -> DomItem { return self.subMapItem(Map( self.pathFromOwner().field(Fields::imported), - [this, &self](DomItem &map, QString key) { + [this, &self](const DomItem &map, const QString &key) { return map.subListItem(List::fromQList<DomItem>( map.pathFromOwner().key(key), importedItemsWithName(self, key), - [](DomItem &, const PathEls::PathComponent &, DomItem &el) { + [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) { return el; })); }, - [this, &self](DomItem &) { return this->importedNames(self); }, + [this, &self](const DomItem &) { return this->importedNames(self); }, QLatin1String("List<Export>"))); }); return cont; } -bool PropertyInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool PropertyInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::propertyDefs, propertyDefs); @@ -1407,7 +1446,7 @@ BindingValue::BindingValue(const QmlObject &o) : kind(BindingValueKind::Object) new (&object) QmlObject(o); } -BindingValue::BindingValue(std::shared_ptr<ScriptExpression> o) +BindingValue::BindingValue(const std::shared_ptr<ScriptExpression> &o) : kind(BindingValueKind::ScriptExpression) { new (&scriptExpression) std::shared_ptr<ScriptExpression>(o); @@ -1458,7 +1497,7 @@ BindingValue &BindingValue::operator=(const BindingValue &o) return *this; } -DomItem BindingValue::value(DomItem &binding) +DomItem BindingValue::value(const DomItem &binding) const { switch (kind) { case BindingValueKind::Empty: @@ -1470,14 +1509,14 @@ DomItem BindingValue::value(DomItem &binding) case BindingValueKind::Array: return binding.subListItem(List::fromQListRef<QmlObject>( binding.pathFromOwner().field(u"value"), array, - [binding](DomItem &self, const PathEls::PathComponent &, QmlObject &obj) { + [](const DomItem &self, const PathEls::PathComponent &, const QmlObject &obj) { return self.copy(&obj); })); } return DomItem(); } -void BindingValue::updatePathFromOwner(Path newPath) +void BindingValue::updatePathFromOwner(const Path &newPath) { switch (kind) { case BindingValueKind::Empty: @@ -1511,10 +1550,10 @@ void BindingValue::clearValue() kind = BindingValueKind::Empty; } -ScriptExpression::ScriptExpression(QStringView code, std::shared_ptr<QQmlJS::Engine> engine, - AST::Node *ast, std::shared_ptr<AstComments> comments, - ExpressionType expressionType, SourceLocation localOffset, - int derivedFrom, QStringView preCode, QStringView postCode) +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), @@ -1547,8 +1586,8 @@ ScriptExpression::ScriptExpression(const ScriptExpression &e) : OwningItem(e) m_astComments = e.m_astComments; } -std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode(DomItem &self, - QString code) const +std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode( + const DomItem &self, const QString &code) const { std::shared_ptr<ScriptExpression> copy = makeCopy(self); DomItem container = self.containingObject(); @@ -1558,7 +1597,7 @@ std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode(DomItem return copy; } -bool ScriptExpression::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool ScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = OwningItem::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::code, code()); @@ -1573,12 +1612,17 @@ bool ScriptExpression::iterateDirectSubpaths(DomItem &self, DirectVisitor visito cont = cont && self.dvValueLazyField( visitor, Fields::localOffset, - [this]() { return locationToData(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; } @@ -1613,19 +1657,24 @@ AST::Node *firstNodeInRange(AST::Node *n, quint32 minStart = 0, quint32 maxEnd = return visitor.firstNodeInRange; } -void ScriptExpression::setCode(QString code, QString preCode, QString postCode) +void ScriptExpression::setCode(const QString &code, const QString &preCode, const QString &postCode) { + // TODO QTBUG-121933 m_codeStr = code; - const bool qmlMode = (m_expressionType == ExpressionType::BindingExpression); - if (qmlMode && preCode.isEmpty()) { - preCode = Binding::preCodeForName(u"binding"); - postCode = Binding::postCodeForName(u"binding"); - } - if (!preCode.isEmpty() || !postCode.isEmpty()) - m_codeStr = preCode + code + postCode; - m_code = QStringView(m_codeStr).mid(preCode.size(), code.size()); - m_preCode = QStringView(m_codeStr).mid(0, preCode.size()); - m_postCode = QStringView(m_codeStr).mid(preCode.size() + code.size(), postCode.size()); + 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(); @@ -1637,26 +1686,17 @@ void ScriptExpression::setCode(QString code, QString preCode, QString postCode) m_localOffset.startLine = preChange.nNewlines; m_engine = std::make_shared<QQmlJS::Engine>(); m_astComments = std::make_shared<AstComments>(m_engine); - QQmlJS::Lexer lexer(m_engine.get()); - lexer.setCode(m_codeStr, /*lineno = */ 1, /*qmlMode=*/true); - QQmlJS::Parser parser(m_engine.get()); - if ((qmlMode && !parser.parse()) || (!qmlMode && !parser.parseScript())) - addErrorLocal(domParsingErrors().error(tr("Parsing of code failed"))); - for (DiagnosticMessage msg : parser.diagnosticMessages()) { - 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(err); - } - m_ast = parser.rootNode(); + 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) @@ -1666,11 +1706,45 @@ void ScriptExpression::setCode(QString code, QString preCode, QString postCode) if (m_expressionType == ExpressionType::BindingExpression) if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast)) m_ast = exp->expression; - AstComments::collectComments(m_engine, m_ast, m_astComments, MutableDomItem(), nullptr); + + 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(Sink s, AstDumperOptions options) const +void ScriptExpression::astDumper(const Sink &s, AstDumperOptions options) const { astNodeDumper(s, ast(), options, 1, 0, [this](SourceLocation astL) { SourceLocation l = this->locationToLocal(astL); @@ -1680,12 +1754,12 @@ void ScriptExpression::astDumper(Sink s, AstDumperOptions options) const QString ScriptExpression::astRelocatableDump() const { - return dumperToString([this](Sink s) { + return dumperToString([this](const Sink &s) { this->astDumper(s, AstDumperOption::NoLocations | AstDumperOption::SloppyCompare); }); } -void ScriptExpression::writeOut(DomItem &self, OutWriter &lw) const +void ScriptExpression::writeOut(const DomItem &self, OutWriter &lw) const { OutWriter *ow = &lw; @@ -1695,6 +1769,14 @@ void ScriptExpression::writeOut(DomItem &self, OutWriter &lw) const 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); @@ -1711,10 +1793,10 @@ void ScriptExpression::writeOut(DomItem &self, OutWriter &lw) const lw.lineWriter.endSourceLocation(*codeLoc); } -SourceLocation ScriptExpression::globalLocation(DomItem &self) const +SourceLocation ScriptExpression::globalLocation(const DomItem &self) const { - if (const FileLocations *fLocPtr = FileLocations::fileLocationsPtr(self)) { - return fLocPtr->regions.value(QString(), fLocPtr->fullRegion); + if (const FileLocations::Tree tree = FileLocations::treeOf(self)) { + return FileLocations::region(tree, MainRegion); } return SourceLocation(); } @@ -1724,23 +1806,23 @@ bool PropertyDefinition::isParametricType() const return typeName.contains(QChar(u'<')); } -void PropertyDefinition::writeOut(DomItem &, OutWriter &lw) const +void PropertyDefinition::writeOut(const DomItem &, OutWriter &lw) const { lw.ensureNewline(); if (isDefaultMember) - lw.writeRegion(u"default").space(); + lw.writeRegion(DefaultKeywordRegion).space(); if (isRequired) - lw.writeRegion(u"required").space(); + lw.writeRegion(RequiredKeywordRegion).space(); if (isReadonly) - lw.writeRegion(u"readonly").space(); + lw.writeRegion(ReadonlyKeywordRegion).space(); if (!typeName.isEmpty()) { - lw.writeRegion(u"property").space(); - lw.writeRegion(u"type", typeName).space(); + lw.writeRegion(PropertyKeywordRegion).space(); + lw.writeRegion(TypeIdentifierRegion, typeName).space(); } - lw.writeRegion(u"name", name); + lw.writeRegion(IdentifierRegion, name); } -bool MethodInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool MethodInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); cont = cont && self.dvWrapField(visitor, Fields::parameters, parameters); @@ -1752,12 +1834,18 @@ bool MethodInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) 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.dvWrapField(visitor, Fields::body, body); + cont = cont && self.dvItemField(visitor, Fields::body, [this, &self]() { + return self.subOwnerItem(PathEls::Field(Fields::body), body); + }); return cont; } -QString MethodInfo::preCode(DomItem &self) const +QString MethodInfo::preCode(const DomItem &self) const { QString res; LineWriter lw([&res](QStringView s) { res.append(s); }, QLatin1String("*preCode*")); @@ -1767,41 +1855,41 @@ QString MethodInfo::preCode(DomItem &self) const MockObject standinObj(self.pathFromOwner()); DomItem standin = self.copy(&standinObj); ow.itemStart(standin); - ow.writeRegion(u"function").space().writeRegion(u"name", name); + ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name); bool first = true; - ow.writeRegion(u"leftParen", u"("); + ow.writeRegion(LeftParenthesisRegion); for (const MethodParameter &mp : parameters) { if (first) first = false; else ow.write(u", "); - ow.write(mp.name); + ow.write(mp.value->code()); } - ow.writeRegion(u"rightParen", u")"); - ow.ensureSpace().writeRegion(u"leftBrace", u"{"); + ow.writeRegion(RightParenthesisRegion); + ow.ensureSpace().writeRegion(LeftBraceRegion); ow.itemEnd(standin); ow.eof(); return res; } -QString MethodInfo::postCode(DomItem &) const +QString MethodInfo::postCode(const DomItem &) const { return QLatin1String("\n}\n"); } -void MethodInfo::writeOut(DomItem &self, OutWriter &ow) const +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(u"signal").space().writeRegion(u"name", name); + ow.writeRegion(SignalKeywordRegion).space().writeRegion(IdentifierRegion, name); if (parameters.isEmpty()) return; bool first = true; - ow.writeRegion(u"leftParen", u"("); + ow.writeRegion(LeftParenthesisRegion); int baseIndent = ow.increaseIndent(); - for (DomItem arg : self.field(Fields::parameters).values()) { + for (const DomItem &arg : self.field(Fields::parameters).values()) { if (first) first = false; else @@ -1811,86 +1899,112 @@ void MethodInfo::writeOut(DomItem &self, OutWriter &ow) const else qCWarning(domLog) << "failed to cast to MethodParameter"; } - ow.writeRegion(u"rightParen", u")"); + ow.writeRegion(RightParenthesisRegion); ow.decreaseIndent(1, baseIndent); return; } break; case MethodType::Method: { - ow.writeRegion(u"function").space().writeRegion(u"name", name); + ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name); bool first = true; - ow.writeRegion(u"leftParen", u"("); - for (DomItem arg : self.field(Fields::parameters).values()) { + 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(u"rightParen", u")"); + ow.writeRegion(RightParenthesisRegion); if (!typeName.isEmpty()) { - ow.writeRegion(u"colon", u":"); + ow.writeRegion(ColonTokenRegion); ow.space(); - ow.writeRegion(u"returnType", typeName); + ow.writeRegion(TypeIdentifierRegion, typeName); } - ow.ensureSpace().writeRegion(u"leftBrace", u"{"); + 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(u"rightBrace", u"}"); + ow.ensureNewline().writeRegion(RightBraceRegion); } break; } } -bool MethodParameter::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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::lookupCppTypePath(typeName)); + && 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(DomItem &self, OutWriter &ow) const +void MethodParameter::writeOut(const DomItem &self, OutWriter &ow) const { - ow.writeRegion(u"name", name); - if (!typeName.isEmpty()) - ow.writeRegion(u"colon", u":").space().writeRegion(u"type", typeName); - if (defaultValue) { - ow.space().writeRegion(u"equal", u"=").space(); - self.subOwnerItem(PathEls::Field(Fields::defaultValue), defaultValue).writeOut(ow); + 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(DomItem &self, OutWriter &ow) const +void MethodParameter::writeOutSignal(const DomItem &self, OutWriter &ow) const { self.writeOutPre(ow); if (!typeName.isEmpty()) - ow.writeRegion(u"type", typeName).space(); - ow.writeRegion(u"name", name); + ow.writeRegion(TypeIdentifierRegion, typeName).space(); + ow.writeRegion(IdentifierRegion, name); self.writeOutPost(ow); } -void Pragma::writeOut(DomItem &, OutWriter &ow) const +void Pragma::writeOut(const DomItem &, OutWriter &ow) const { ow.ensureNewline(); - ow.writeRegion(u"pragma").space().writeRegion(u"name", name); + 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(DomItem &self, DirectVisitor visitor) +bool EnumItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::name, name()); @@ -1899,30 +2013,19 @@ bool EnumItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -void EnumItem::writeOut(DomItem &self, OutWriter &ow) const +void EnumItem::writeOut(const DomItem &self, OutWriter &ow) const { ow.ensureNewline(); - ow.writeRegion(u"name", name()); - bool hasDefaultValue = false; + ow.writeRegion(IdentifierRegion, name()); 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) { + if (m_valueKind == ValueKind::ExplicitValue) { QString v = QString::number(value(), 'f', 0); if (abs(value() - v.toDouble()) > 1.e-10) v = QString::number(value()); - ow.space().writeRegion(u"equal", u"=").space().writeRegion(u"value", v); + ow.space().writeRegion(EqualTokenRegion).space().writeRegion(EnumValueRegion, v); } if (myIndex >= 0 && self.container().indexes() != myIndex + 1) - ow.writeRegion(u"comma", u","); + ow.writeRegion(CommaTokenRegion); } QmlUri QmlUri::fromString(const QString &str) @@ -2069,6 +2172,11 @@ QmlUri::Kind QmlUri::kind() const return m_kind; } +void ScriptExpression::setScriptElement(const ScriptElementVariant &p) +{ + m_element = p; +} + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h index cbf22a0018..95cddbbc32 100644 --- a/src/qmldom/qqmldomelements_p.h +++ b/src/qmldom/qqmldomelements_p.h @@ -22,17 +22,15 @@ #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> -#ifdef QMLDOM_STANDALONE -# include "qmlcompiler/qqmljsscope_p.h" -#else -# include <private/qqmljsscope_p.h> -#endif +#include <memory> +#include <private/qqmljsscope_p.h> #include <functional> #include <limits> @@ -45,38 +43,42 @@ 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(QString uri, int majorVersion, ErrorHandler errorHandler = nullptr); -Path moduleScopePath(QString uri, Version version, ErrorHandler errorHandler = nullptr); -Path moduleScopePath(QString uri, QString version, ErrorHandler errorHandler = nullptr); -inline Path moduleScopePath(QString uri, ErrorHandler errorHandler = nullptr) +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(QString path) +inline Path qmlDirInfoPath(const QString &path) { return Path::Root(PathRoot::Top).field(Fields::qmldirWithPath).key(path); } -inline Path qmlDirPath(QString path) +inline Path qmlDirPath(const QString &path) { return qmlDirInfoPath(path).field(Fields::currentItem); } -inline Path qmldirFileInfoPath(QString path) +inline Path qmldirFileInfoPath(const QString &path) { return Path::Root(PathRoot::Top).field(Fields::qmldirFileWithPath).key(path); } -inline Path qmldirFilePath(QString path) +inline Path qmldirFilePath(const QString &path) { return qmldirFileInfoPath(path).field(Fields::currentItem); } -inline Path qmlFileInfoPath(QString canonicalFilePath) +inline Path qmlFileInfoPath(const QString &canonicalFilePath) { return Path::Root(PathRoot::Top).field(Fields::qmlFileWithPath).key(canonicalFilePath); } -inline Path qmlFilePath(QString canonicalFilePath) +inline Path qmlFilePath(const QString &canonicalFilePath) { return qmlFileInfoPath(canonicalFilePath).field(Fields::currentItem); } -inline Path qmlFileObjectPath(QString canonicalFilePath) +inline Path qmlFileObjectPath(const QString &canonicalFilePath) { return qmlFilePath(canonicalFilePath) .field(Fields::components) @@ -85,55 +87,55 @@ inline Path qmlFileObjectPath(QString canonicalFilePath) .field(Fields::objects) .index(0); } -inline Path qmltypesFileInfoPath(QString path) +inline Path qmltypesFileInfoPath(const QString &path) { return Path::Root(PathRoot::Top).field(Fields::qmltypesFileWithPath).key(path); } -inline Path qmltypesFilePath(QString path) +inline Path qmltypesFilePath(const QString &path) { return qmltypesFileInfoPath(path).field(Fields::currentItem); } -inline Path jsFileInfoPath(QString path) +inline Path jsFileInfoPath(const QString &path) { return Path::Root(PathRoot::Top).field(Fields::jsFileWithPath).key(path); } -inline Path jsFilePath(QString path) +inline Path jsFilePath(const QString &path) { return jsFileInfoPath(path).field(Fields::currentItem); } -inline Path qmlDirectoryInfoPath(QString path) +inline Path qmlDirectoryInfoPath(const QString &path) { return Path::Root(PathRoot::Top).field(Fields::qmlDirectoryWithPath).key(path); } -inline Path qmlDirectoryPath(QString path) +inline Path qmlDirectoryPath(const QString &path) { return qmlDirectoryInfoPath(path).field(Fields::currentItem); } -inline Path globalScopeInfoPath(QString name) +inline Path globalScopeInfoPath(const QString &name) { return Path::Root(PathRoot::Top).field(Fields::globalScopeWithName).key(name); } -inline Path globalScopePath(QString name) +inline Path globalScopePath(const QString &name) { return globalScopeInfoPath(name).field(Fields::currentItem); } -inline Path lookupCppTypePath(QString name) +inline Path lookupCppTypePath(const QString &name) { return Path::Current(PathCurrent::Lookup).field(Fields::cppType).key(name); } -inline Path lookupPropertyPath(QString name) +inline Path lookupPropertyPath(const QString &name) { return Path::Current(PathCurrent::Lookup).field(Fields::propertyDef).key(name); } -inline Path lookupSymbolPath(QString name) +inline Path lookupSymbolPath(const QString &name) { return Path::Current(PathCurrent::Lookup).field(Fields::symbol).key(name); } -inline Path lookupTypePath(QString name) +inline Path lookupTypePath(const QString &name) { return Path::Current(PathCurrent::Lookup).field(Fields::type).key(name); } -inline Path loadInfoPath(Path el) +inline Path loadInfoPath(const Path &el) { return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(el.toString()); } @@ -142,12 +144,12 @@ inline Path loadInfoPath(Path el) class QMLDOM_EXPORT CommentableDomElement : public DomElement { public: - CommentableDomElement(Path pathFromOwner = Path()) : DomElement(pathFromOwner) { } + 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(DomItem &self, DirectVisitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; RegionComments &comments() { return m_comments; } const RegionComments &comments() const { return m_comments; } @@ -165,7 +167,7 @@ public: Version(qint32 majorVersion = Undefined, qint32 minorVersion = Undefined); static Version fromString(QStringView v); - bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; bool isLatest() const; bool isValid() const; @@ -264,17 +266,20 @@ class QMLDOM_EXPORT Import public: constexpr static DomType kindValue = DomType::Import; - static Import fromUriString(QString importStr, Version v = Version(), - QString importId = QString(), ErrorHandler handler = nullptr); - static Import fromFileString(QString importStr, QString importId = QString(), - ErrorHandler handler = nullptr); + 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(QmlUri uri = QmlUri(), Version version = Version(), QString importId = QString()) + Import(const QmlUri &uri = QmlUri(), Version version = Version(), + const QString &importId = QString()) : uri(uri), version(version), importId(importId) { } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; Path importedPath() const { if (uri.isDirectory()) { @@ -298,7 +303,7 @@ public: } friend bool operator!=(const Import &i1, const Import &i2) { return !(i1 == i2); } - void writeOut(DomItem &self, OutWriter &ow) const; + void writeOut(const DomItem &self, OutWriter &ow) const; static QRegularExpression importRe(); @@ -314,7 +319,7 @@ class QMLDOM_EXPORT ModuleAutoExport public: constexpr static DomType kindValue = DomType::ModuleAutoExport; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvWrapField(visitor, Fields::import, import); @@ -340,18 +345,23 @@ class QMLDOM_EXPORT Pragma public: constexpr static DomType kindValue = DomType::Pragma; - Pragma(QString pragmaName = QString()) : name(pragmaName) { } + Pragma(const QString &pragmaName = QString(), const QStringList &pragmaValues = {}) + : name(pragmaName), values{ pragmaValues } + { + } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + 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(DomItem &self, OutWriter &ow) const; + void writeOut(const DomItem &self, OutWriter &ow) const; QString name; + QStringList values; RegionComments comments; }; @@ -360,34 +370,45 @@ class QMLDOM_EXPORT Id public: constexpr static DomType kindValue = DomType::Id; - Id(QString idName = QString(), Path referredObject = Path()); + Id(const QString &idName = QString(), const Path &referredObject = Path()); - bool iterateDirectSubpaths(DomItem &self, DirectVisitor); - void updatePathFromOwner(Path pathFromOwner); - Path addAnnotation(Path selfPathFromOwner, const QmlObject &ann, QmlObject **aPtr = nullptr); + 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 }; + 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, std::shared_ptr<QQmlJS::Engine> engine, - AST::Node *ast, std::shared_ptr<AstComments> comments, - ExpressionType expressionType, - SourceLocation localOffset = SourceLocation(), int derivedFrom = 0, - QStringView preCode = QStringView(), - QStringView postCode = QStringView()); + 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, @@ -396,8 +417,9 @@ public: { } - explicit ScriptExpression(QString code, ExpressionType expressionType, int derivedFrom = 0, - QString preCode = QString(), QString postCode = QString()) + 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); @@ -405,20 +427,20 @@ public: ScriptExpression(const ScriptExpression &e); - std::shared_ptr<ScriptExpression> makeCopy(DomItem &self) const + std::shared_ptr<ScriptExpression> makeCopy(const DomItem &self) const { return std::static_pointer_cast<ScriptExpression>(doCopy(self)); } - std::shared_ptr<ScriptExpression> copyWithUpdatedCode(DomItem &self, QString code) const; + std::shared_ptr<ScriptExpression> copyWithUpdatedCode(const DomItem &self, const QString &code) const; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; - Path canonicalPath(DomItem &self) const override { return self.m_ownerPath; } + 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(Sink s, AstDumperOptions options) const; + void astDumper(const Sink &s, AstDumperOptions options) const; QString astRelocatableDump() const; // definedSymbols name, value, from @@ -446,19 +468,21 @@ public: return m_engine; } std::shared_ptr<AstComments> astComments() const { return m_astComments; } - void writeOut(DomItem &self, OutWriter &lw) const override; - SourceLocation globalLocation(DomItem &self) const; + 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(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { return std::make_shared<ScriptExpression>(*this); } - std::function<SourceLocation(SourceLocation)> locationToGlobalF(DomItem &self) const + std::function<SourceLocation(SourceLocation)> locationToGlobalF(const DomItem &self) const { SourceLocation loc = globalLocation(self); return [loc, this](SourceLocation x) { @@ -479,13 +503,34 @@ protected: : x.startColumn)); // are line and column 1 based? then we should + 1 } - std::function<SourceLocation(SourceLocation)> locationToLocalF(DomItem &) const + std::function<SourceLocation(SourceLocation)> locationToLocalF(const DomItem &) const { return [this](SourceLocation x) { return locationToLocal(x); }; } private: - void setCode(QString code, QString preCode, QString postCode); + 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; @@ -495,6 +540,7 @@ private: mutable AST::Node *m_ast; std::shared_ptr<AstComments> m_astComments; SourceLocation m_localOffset; + ScriptElementVariant m_element; }; class BindingValue; @@ -504,22 +550,25 @@ class QMLDOM_EXPORT Binding public: constexpr static DomType kindValue = DomType::Binding; - Binding(QString m_name = QString(), + Binding(const QString &m_name = QString(), std::unique_ptr<BindingValue> value = std::unique_ptr<BindingValue>(), BindingType bindingType = BindingType::Normal); - Binding(QString m_name, std::shared_ptr<ScriptExpression> value, + 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(QString m_name, QString scriptCode, BindingType bindingType = BindingType::Normal); - Binding(QString m_name, QmlObject value, BindingType bindingType = BindingType::Normal); - Binding(QString m_name, 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(DomItem &self, DirectVisitor); - DomItem valueItem(DomItem &self) const; // ### REVISIT: consider replacing return value with variant + 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; } @@ -530,20 +579,18 @@ public: QList<QmlObject> *arrayValue(); std::shared_ptr<ScriptExpression> scriptExpressionValue(); QList<QmlObject> annotations() const { return m_annotations; } - void setAnnotations(QList<QmlObject> annotations) { m_annotations = annotations; } + void setAnnotations(const QList<QmlObject> &annotations) { m_annotations = annotations; } void setValue(std::unique_ptr<BindingValue> &&value) { m_value = std::move(value); } - Path addAnnotation(Path selfPathFromOwner, const QmlObject &a, QmlObject **aPtr = nullptr); + 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(Path newPath); - void writeOut(DomItem &self, OutWriter &lw) const; - void writeOutValue(DomItem &self, OutWriter &lw) const; + 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(); - if (baseName.startsWith(u"on") && baseName.size() > 2 && baseName.at(2).isUpper()) - return true; - return false; + return QQmlSignalNames::isHandlerName(baseName); } static QString preCodeForName(QStringView n) { @@ -553,13 +600,17 @@ public: 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 QmlDomAstCreator; + 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 @@ -567,11 +618,14 @@ class QMLDOM_EXPORT AttributeInfo public: enum Access { Private, Protected, Public }; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; - Path addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, + Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr = nullptr); - void updatePathFromOwner(Path newPath); + 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; @@ -580,6 +634,7 @@ public: bool isList = false; QList<QmlObject> annotations; RegionComments comments; + QQmlJSScope::ConstPtr m_semanticScope; }; struct QMLDOM_EXPORT LocallyResolvedAlias @@ -608,7 +663,7 @@ class QMLDOM_EXPORT PropertyDefinition : public AttributeInfo public: constexpr static DomType kindValue = DomType::PropertyDefinition; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer); @@ -621,6 +676,11 @@ public: 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; } @@ -628,7 +688,9 @@ public: bool isAlias() const { return typeName == u"alias"; } bool isParametricType() const; - void writeOut(DomItem &self, OutWriter &lw) 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; @@ -638,6 +700,7 @@ public: bool isPointer = false; bool isDefaultMember = false; bool isRequired = false; + ScriptElementVariant m_nameIdentifiers; }; class QMLDOM_EXPORT PropertyInfo @@ -645,7 +708,7 @@ class QMLDOM_EXPORT PropertyInfo public: constexpr static DomType kindValue = DomType::PropertyInfo; // used to get the correct kind in ObjectWrapper - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; QList<DomItem> propertyDefs; QList<DomItem> bindings; @@ -656,17 +719,24 @@ class QMLDOM_EXPORT MethodParameter public: constexpr static DomType kindValue = DomType::MethodParameter; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; - void writeOut(DomItem &self, OutWriter &ow) const; - void writeOutSignal(DomItem &self, OutWriter &ow) 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; }; @@ -680,28 +750,29 @@ public: constexpr static DomType kindValue = DomType::MethodInfo; - Path typePath(DomItem &) const + Path typePath(const DomItem &) const { return (typeName.isEmpty() ? Path() : Paths::lookupTypePath(typeName)); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); - QString preCode(DomItem &) const; // ### REVISIT, might be simplified by using different toplevel production rules at usage site - QString postCode(DomItem &) const; - void writePre(DomItem &self, OutWriter &ow) const; - void writeOut(DomItem &self, OutWriter &ow) const; - void setCode(QString code) + 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; }; @@ -709,20 +780,27 @@ class QMLDOM_EXPORT EnumItem { public: constexpr static DomType kindValue = DomType::EnumItem; + enum class ValueKind : quint8 { + ImplicitValue, + ExplicitValue + }; + EnumItem(const QString &name = QString(), int value = 0, ValueKind valueKind = ValueKind::ImplicitValue) + : m_name(name), m_value(value), m_valueKind(valueKind) + { + } - EnumItem(QString name = QString(), int value = 0) : m_name(name), m_value(value) { } - - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + 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(DomItem &self, OutWriter &lw) const; + void writeOut(const DomItem &self, OutWriter &lw) const; private: QString m_name; double m_value; + ValueKind m_valueKind; RegionComments m_comments; }; @@ -732,33 +810,33 @@ public: constexpr static DomType kindValue = DomType::EnumDecl; DomType kind() const override { return kindValue; } - EnumDecl(QString name = QString(), QList<EnumItem> values = QList<EnumItem>(), + EnumDecl(const QString &name = QString(), QList<EnumItem> values = QList<EnumItem>(), Path pathFromOwner = Path()) : CommentableDomElement(pathFromOwner), m_name(name), m_values(values) { } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; QString name() const { return m_name; } - void setName(QString name) { m_name = 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(QString aliasName) { m_alias = aliasName; } + 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(Path newP) override; + void updatePathFromOwner(const Path &newP) override; const QList<QmlObject> &annotations() const & { return m_annotations; } - void setAnnotations(QList<QmlObject> annotations); + void setAnnotations(const QList<QmlObject> &annotations); Path addAnnotation(const QmlObject &child, QmlObject **cPtr = nullptr); - void writeOut(DomItem &self, OutWriter &lw) const override; + void writeOut(const DomItem &self, OutWriter &lw) const override; private: QString m_name; @@ -775,20 +853,16 @@ public: constexpr static DomType kindValue = DomType::QmlObject; DomType kind() const override { return kindValue; } - QmlObject(Path pathFromOwner = Path()); - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - bool iterateBaseDirectSubpaths(DomItem &self, DirectVisitor); + 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(DomItem &) const override { return fields(); } - DomItem field(DomItem &self, QStringView name); - DomItem field(DomItem &self, QStringView name) const override - { - return const_cast<QmlObject *>(this)->field(self, name); - } - void updatePathFromOwner(Path newPath) override; + 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(DomItem &self) const; - virtual bool iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor) 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; } @@ -800,11 +874,11 @@ public: QList<QmlObject> children() const { return m_children; } QList<QmlObject> annotations() const { return m_annotations; } - void setIdStr(QString id) { m_idStr = id; } - void setName(QString name) { m_name = name; } - void setDefaultPropertyName(QString name) { m_defaultPropertyName = name; } + 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(Path prototypePath) + Path addPrototypePath(const Path &prototypePath) { index_type idx = index_type(m_prototypePaths.indexOf(prototypePath)); if (idx == -1) { @@ -813,33 +887,33 @@ public: } return Path::Field(Fields::prototypes).index(idx); } - void setNextScopePath(Path nextScopePath) { m_nextScopePath = nextScopePath; } + 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(QList<QmlObject> children) + void setChildren(const QList<QmlObject> &children) { m_children = children; if (pathFromOwner()) updatePathFromOwner(pathFromOwner()); } - void setAnnotations(QList<QmlObject> annotations) + void setAnnotations(const QList<QmlObject> &annotations) { m_annotations = annotations; if (pathFromOwner()) updatePathFromOwner(pathFromOwner()); } - Path addPropertyDef(PropertyDefinition propertyDef, AddOption option, + 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, PropertyDefinition propertyDef, + MutableDomItem addPropertyDef(MutableDomItem &self, const PropertyDefinition &propertyDef, AddOption option); Path addBinding(Binding binding, AddOption option, Binding **bPtr = nullptr) @@ -848,12 +922,12 @@ public: binding.name(), binding, option, bPtr); } MutableDomItem addBinding(MutableDomItem &self, Binding binding, AddOption option); - Path addMethod(MethodInfo functionDef, AddOption option, MethodInfo **mPtr = nullptr) + 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, MethodInfo functionDef, AddOption option); + MutableDomItem addMethod(MutableDomItem &self, const MethodInfo &functionDef, AddOption option); Path addChild(QmlObject child, QmlObject **cPtr = nullptr) { return appendUpdatableElementInQList(pathFromOwner().field(Fields::children), m_children, @@ -869,15 +943,21 @@ public: return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), m_annotations, annotation, aPtr); } - void writeOut(DomItem &self, OutWriter &ow, QString onTarget) const; - void writeOut(DomItem &self, OutWriter &lw) const override { writeOut(self, lw, QString()); } + 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(DomItem &self, + LocallyResolvedAlias resolveAlias(const DomItem &self, std::shared_ptr<ScriptExpression> accessSequence) const; - LocallyResolvedAlias resolveAlias(DomItem &self, const QStringList &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 QmlDomAstCreator; + friend class QQmlDomAstCreator; QString m_idStr; QString m_name; QList<Path> m_prototypePaths; @@ -888,6 +968,8 @@ private: QMultiMap<QString, MethodInfo> m_methods; QList<QmlObject> m_children; QList<QmlObject> m_annotations; + QQmlJSScope::ConstPtr m_scope; + ScriptElementVariant m_nameIdentifiers; }; class Export @@ -895,8 +977,9 @@ class Export Q_DECLARE_TR_FUNCTIONS(Export) public: constexpr static DomType kindValue = DomType::Export; - static Export fromString(Path source, QStringView exp, Path typePath, ErrorHandler h); - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + 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); @@ -923,18 +1006,14 @@ public: class QMLDOM_EXPORT Component : public CommentableDomElement { public: - Component(QString name); - Component(Path pathFromOwner = Path()); + Component(const QString &name); + Component(const Path &pathFromOwner = Path()); Component(const Component &o) = default; Component &operator=(const Component &) = default; - bool iterateDirectSubpaths(DomItem &, DirectVisitor) override; - void updatePathFromOwner(Path newPath) override; - DomItem field(DomItem &self, QStringView name) const override - { - return const_cast<Component *>(this)->field(self, name); - } - DomItem field(DomItem &self, QStringView name); + 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; } @@ -943,9 +1022,9 @@ public: bool isCreatable() const { return m_isCreatable; } bool isComposite() const { return m_isComposite; } QString attachedTypeName() const { return m_attachedTypeName; } - Path attachedTypePath(DomItem &) const { return m_attachedTypePath; } + Path attachedTypePath(const DomItem &) const { return m_attachedTypePath; } - void setName(QString name) { m_name = name; } + void setName(const QString &name) { m_name = name; } void setEnumerations(QMultiMap<QString, EnumDecl> enumerations) { m_enumerations = enumerations; @@ -957,16 +1036,16 @@ public: m_enumerations, enumeration.name(), enumeration, option, ePtr); } - void setObjects(QList<QmlObject> objects) { m_objects = objects; } + 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(QString name) { m_attachedTypeName = name; } - void setAttachedTypePath(Path p) { m_attachedTypePath = p; } + void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; } + void setAttachedTypePath(const Path &p) { m_attachedTypePath = p; } private: - friend class QmlDomAstCreator; + friend class QQmlDomAstCreator; QString m_name; QMultiMap<QString, EnumDecl> m_enumerations; QList<QmlObject> m_objects; @@ -983,8 +1062,8 @@ public: constexpr static DomType kindValue = DomType::JsResource; DomType kind() const override { return kindValue; } - JsResource(Path pathFromOwner = Path()) : Component(pathFromOwner) { } - bool iterateDirectSubpaths(DomItem &, DirectVisitor) override + JsResource(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { } + bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override { // to do: complete return true; } @@ -997,13 +1076,13 @@ public: constexpr static DomType kindValue = DomType::QmltypesComponent; DomType kind() const override { return kindValue; } - QmltypesComponent(Path pathFromOwner = Path()) : Component(pathFromOwner) { } - bool iterateDirectSubpaths(DomItem &, DirectVisitor) override; + 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(QString fileName) { m_fileName = fileName; } + 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; } @@ -1014,20 +1093,28 @@ public: 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 m_accessSemantics = QQmlJSScope::AccessSemantics::None; + QQmlJSScope::ConstPtr m_semanticScope; }; class QMLDOM_EXPORT QmlComponent final : public Component @@ -1036,39 +1123,42 @@ public: constexpr static DomType kindValue = DomType::QmlComponent; DomType kind() const override { return kindValue; } - QmlComponent(QString name = QString()) : Component(name) + QmlComponent(const QString &name = QString()) : Component(name) { setIsComposite(true); setIsCreatable(true); } - QmlComponent(const QmlComponent &o) - : Component(o), m_nextComponentPath(o.m_nextComponentPath), m_ids(o.m_ids) - { - } - QmlComponent &operator=(const QmlComponent &) = default; - - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + 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(Path p) { m_nextComponentPath = p; } - void updatePathFromOwner(Path newPath) override; + 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(DomItem &self, OutWriter &) const override; - QList<QString> subComponentsNames(DomItem &self) const; - QList<DomItem> subComponents(DomItem &self) const; + 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 QmlDomAstCreator; + 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 @@ -1077,7 +1167,7 @@ public: constexpr static DomType kindValue = DomType::GlobalComponent; DomType kind() const override { return kindValue; } - GlobalComponent(Path pathFromOwner = Path()) : Component(pathFromOwner) { } + GlobalComponent(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { } }; static ErrorGroups importErrors = { { DomItem::domErrorGroup, NewErrorGroup("importError") } }; @@ -1095,22 +1185,24 @@ public: const QMap<QString, ImportScope> &subImports() const & { return m_subImports; } - QList<Path> allSources(DomItem &self) const; + QList<Path> allSources(const DomItem &self) const; - QSet<QString> importedNames(DomItem &self) const + QSet<QString> importedNames(const DomItem &self) const { QSet<QString> res; - for (Path p : allSources(self)) { + 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(DomItem &self, QString name) const + QList<DomItem> importedItemsWithName(const DomItem &self, const QString &name) const { QList<DomItem> res; - for (Path p : allSources(self)) { + 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(); @@ -1125,10 +1217,10 @@ public: return res; } - QList<Export> importedExportsWithName(DomItem &self, QString name) const + QList<Export> importedExportsWithName(const DomItem &self, const QString &name) const { QList<Export> res; - for (DomItem &i : importedItemsWithName(self, name)) + for (const DomItem &i : importedItemsWithName(self, name)) if (const Export *e = i.as<Export>()) res.append(*e); else @@ -1137,13 +1229,13 @@ public: return res; } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; - void addImport(QStringList p, Path targetExports) + void addImport(QStringList p, const Path &targetExports) { if (!p.isEmpty()) { - QString current = p.takeFirst(); - m_subImports[current].addImport(p, targetExports); + const QString current = p.takeFirst(); + m_subImports[current].addImport(std::move(p), targetExports); } else if (!m_importSourcePaths.contains(targetExports)) { m_importSourcePaths.append(targetExports); } @@ -1159,14 +1251,14 @@ class BindingValue public: BindingValue(); BindingValue(const QmlObject &o); - BindingValue(std::shared_ptr<ScriptExpression> 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(DomItem &binding); - void updatePathFromOwner(Path newPath); + DomItem value(const DomItem &binding) const; + void updatePathFromOwner(const Path &newPath); private: friend class Binding; diff --git a/src/qmldom/qqmldomerrormessage.cpp b/src/qmldom/qqmldomerrormessage.cpp index d4b95850e2..b48702822f 100644 --- a/src/qmldom/qqmldomerrormessage.cpp +++ b/src/qmldom/qqmldomerrormessage.cpp @@ -37,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())); @@ -70,13 +70,13 @@ 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.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.size(); ++i) groups.at(i).dumpId(sink); @@ -138,14 +138,16 @@ 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 == SourceLocation() @@ -156,7 +158,9 @@ ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, Path elemen 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]; @@ -192,42 +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::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::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); } @@ -248,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); @@ -287,6 +305,10 @@ struct StorableMsg { msg(e) {} + StorableMsg(ErrorMessage &&e): + msg(std::move(e)) + {} + ErrorMessage msg; }; @@ -296,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"); @@ -312,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(function_ref<bool (ErrorMessage)> visitor) +void ErrorMessage::visitRegisteredMessages(function_ref<bool(const ErrorMessage &)> visitor) { QHash<QLatin1String, StorableMsg> r; { @@ -336,7 +358,7 @@ void ErrorMessage::visitRegisteredMessages(function_ref<bool (ErrorMessage)> vis 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)); }); { @@ -363,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; @@ -381,15 +403,15 @@ 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 == SourceLocation()) { - if (const FileLocations *fLocPtr = FileLocations::fileLocationsPtr(el)) { - location = fLocPtr->regions.value(QString(), fLocPtr->fullRegion); + if (const FileLocations::Tree tree = FileLocations::treeOf(el)) { + location = FileLocations::region(tree, MainRegion); } } return *this; @@ -404,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); @@ -436,7 +458,7 @@ void ErrorMessage::dump(Sink sink) const 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 @@ -463,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); } /*! @@ -474,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) { @@ -497,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); } diff --git a/src/qmldom/qqmldomerrormessage_p.h b/src/qmldom/qqmldomerrormessage_p.h index ad9c1d56ec..20e2d817e0 100644 --- a/src/qmldom/qqmldomerrormessage_p.h +++ b/src/qmldom/qqmldomerrormessage_p.h @@ -51,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; @@ -63,23 +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 warning(QString message) const; - [[nodiscard]] ErrorMessage warning(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); @@ -99,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(function_ref<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> @@ -111,19 +116,24 @@ 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) @@ -203,7 +213,7 @@ 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 d46c258f94..6f48aa19e3 100644 --- a/src/qmldom/qqmldomexternalitems.cpp +++ b/src/qmldom/qqmldomexternalitems.cpp @@ -6,6 +6,7 @@ #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> @@ -15,7 +16,6 @@ #include <QtCore/QDir> #include <QtCore/QScopeGuard> #include <QtCore/QFileInfo> -#include <QtCore/QRegularExpression> #include <QtCore/QRegularExpressionMatch> #include <algorithm> @@ -27,15 +27,16 @@ using namespace Qt::StringLiterals; namespace QQmlJS { namespace Dom { -ExternalOwningItem::ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path path, - int derivedFrom, QString code) +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(DomItem &) const +QString ExternalOwningItem::canonicalFilePath(const DomItem &) const { return m_canonicalFilePath; } @@ -45,7 +46,7 @@ QString ExternalOwningItem::canonicalFilePath() const return m_canonicalFilePath; } -Path ExternalOwningItem::canonicalPath(DomItem &) const +Path ExternalOwningItem::canonicalPath(const DomItem &) const { return m_path; } @@ -62,7 +63,7 @@ ErrorGroups QmldirFile::myParsingErrors() return res; } -std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(QString path, QString code) +std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(const QString &path, const QString &code) { QString canonicalFilePath = QFileInfo(path).canonicalFilePath(); @@ -158,8 +159,11 @@ void QmldirFile::setFromQmldir() } for (QQmlDirParser::Import const &imp : m_qmldir.dependencies()) { QString uri = imp.module; - if (imp.flags & QQmlDirParser::Import::Auto) - qWarning() << "qmldir contains dependency with auto keyword"; + 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() @@ -192,9 +196,9 @@ void QmldirFile::setFromQmldir() bool hasErrors = false; for (auto const &el : m_qmldir.errors(uri().toString())) { ErrorMessage msg = myParsingErrors().errorMessage(el); - addErrorLocal(msg); 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(); @@ -210,7 +214,7 @@ void QmldirFile::setAutoExports(const QList<ModuleAutoExport> &autoExport) m_autoExports = autoExport; } -void QmldirFile::ensureInModuleIndex(DomItem &self, QString uri) +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 @@ -226,17 +230,17 @@ void QmldirFile::ensureInModuleIndex(DomItem &self, QString uri) } } -QCborValue pluginData(QQmlDirParser::Plugin &pl, QStringList cNames) +QCborValue pluginData(const QQmlDirParser::Plugin &pl, const QStringList &cNames) { QCborArray names; - for (QString n : cNames) + 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(DomItem &self, DirectVisitor visitor) +bool QmldirFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::uri, uri().toString()); @@ -248,8 +252,8 @@ bool QmldirFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) QStringList cNames = classNames(); return self.subListItem(List::fromQListRef<QQmlDirParser::Plugin>( self.pathFromOwner().field(Fields::plugins), m_plugins, - [cNames](DomItem &list, const PathEls::PathComponent &p, - QQmlDirParser::Plugin &plugin) { + [cNames](const DomItem &list, const PathEls::PathComponent &p, + const QQmlDirParser::Plugin &plugin) { return list.subDataItem(p, pluginData(plugin, cNames)); })); }); @@ -259,7 +263,7 @@ bool QmldirFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) const QMap<QString, QString> typeFileMap = qmlFiles(); return self.subMapItem(Map( self.pathFromOwner().field(Fields::qmlFiles), - [typeFileMap](DomItem &map, QString typeV) { + [typeFileMap](const DomItem &map, const QString &typeV) { QString path = typeFileMap.value(typeV); if (path.isEmpty()) return DomItem(); @@ -268,7 +272,7 @@ bool QmldirFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) PathEls::Key(typeV), QList<Path>({ Paths::qmlFileObjectPath(path) })); }, - [typeFileMap](DomItem &) { + [typeFileMap](const DomItem &) { return QSet<QString>(typeFileMap.keyBegin(), typeFileMap.keyEnd()); }, QStringLiteral(u"QList<Reference>"))); @@ -289,41 +293,172 @@ QMap<QString, QString> QmldirFile::qmlFiles() const return res; } -std::shared_ptr<OwningItem> QmlFile::doCopy(DomItem &) const +JsFile::JsFile( + const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt, + int derivedFrom) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(filePath), derivedFrom, + code) { - auto res = std::make_shared<QmlFile>(*this); + 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; } -QmlFile::QmlFile(const QmlFile &o) - : ExternalOwningItem(o), - m_engine(o.m_engine), - m_ast(o.m_ast), - m_astComments(o.m_astComments), - m_comments(o.m_comments), - m_fileLocationsTree(o.m_fileLocationsTree), - m_components(o.m_components), - m_pragmas(o.m_pragmas), - m_imports(o.m_imports), - m_importScope(o.m_importScope) +bool JsFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { - if (m_astComments) - m_astComments = std::make_shared<AstComments>(*m_astComments); + 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); + } } -QmlFile::QmlFile(QString filePath, QString code, QDateTime lastDataUpdateAt, int derivedFrom) +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), - m_astComments(new AstComments(m_engine)), - m_fileLocationsTree(FileLocations::createTree(canonicalPath())) + 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(); - for (DiagnosticMessage msg : parser.diagnosticMessages()) - addErrorLocal(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path)); + 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(); } @@ -334,34 +469,38 @@ ErrorGroups QmlFile::myParsingErrors() return res; } -bool QmlFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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, m_components); - cont = cont && self.dvWrapField(visitor, Fields::pragmas, m_pragmas); - cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports); - cont = cont && self.dvWrapField(visitor, Fields::importScope, m_importScope); - cont = cont && self.dvWrapField(visitor, Fields::fileLocationsTree, m_fileLocationsTree); - cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); - cont = cont && self.dvWrapField(visitor, Fields::astComments, m_astComments); + 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(DomItem &self, QStringView name) +DomItem QmlFile::field(const DomItem &self, QStringView name) const { + ensurePopulated(); if (name == Fields::components) - return self.wrapField(Fields::components, m_components); + return self.wrapField(Fields::components, lazyMembers().m_components); return DomBase::field(self, name); } -void QmlFile::addError(DomItem &self, ErrorMessage msg) +void QmlFile::addError(const DomItem &self, ErrorMessage &&msg) { - self.containingObject().addError(msg); + self.containingObject().addError(std::move(msg)); } -void QmlFile::writeOut(DomItem &self, OutWriter &ow) const +void QmlFile::writeOut(const DomItem &self, OutWriter &ow) const { - for (DomItem &p : self.field(Fields::pragmas).values()) { + ensurePopulated(); + for (const DomItem &p : self.field(Fields::pragmas).values()) { p.writeOut(ow); } for (auto i : self.field(Fields::imports).values()) { @@ -372,20 +511,20 @@ void QmlFile::writeOut(DomItem &self, OutWriter &ow) const mainC.writeOut(ow); } -std::shared_ptr<OwningItem> GlobalScope::doCopy(DomItem &self) const +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(DomItem &self, DirectVisitor visitor) +bool GlobalScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); return cont; } -void QmltypesFile::ensureInModuleIndex(DomItem &self) +void QmltypesFile::ensureInModuleIndex(const DomItem &self) const { auto it = m_uris.begin(); auto end = m_uris.end(); @@ -403,7 +542,7 @@ void QmltypesFile::ensureInModuleIndex(DomItem &self) } } -bool QmltypesFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool QmltypesFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); cont = cont && self.dvWrapField(visitor, Fields::components, m_components); @@ -411,30 +550,31 @@ bool QmltypesFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::uris, [this, &self]() { return self.subMapItem(Map::fromMapRef<QSet<int>>( self.pathFromOwner().field(Fields::uris), m_uris, - [](DomItem &map, const PathEls::PathComponent &p, QSet<int> &el) { + [](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, - [](DomItem &list, const PathEls::PathComponent &p, - int &el) { return list.subDataItem(p, el); })); + [](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(QString filePath, QStringList dirList, QDateTime lastDataUpdateAt, - int derivedFrom) +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 (QString f : dirList) { + for (const QString &f : dirList) { addQmlFilePath(f); } } -bool QmlDirectory::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool QmlDirectory::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); @@ -442,7 +582,7 @@ bool QmlDirectory::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) QDir baseDir(canonicalFilePath()); return self.subMapItem(Map( self.pathFromOwner().field(Fields::qmlFiles), - [this, baseDir](DomItem &map, QString key) -> DomItem { + [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) { @@ -452,7 +592,7 @@ bool QmlDirectory::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) } return map.subReferencesItem(PathEls::Key(key), res); }, - [this](DomItem &) { + [this](const DomItem &) { auto keys = m_qmlFiles.keys(); return QSet<QString>(keys.begin(), keys.end()); }, @@ -461,11 +601,13 @@ bool QmlDirectory::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -bool QmlDirectory::addQmlFilePath(QString relativePath) +bool QmlDirectory::addQmlFilePath(const QString &relativePath) { - QRegularExpression qmlFileRe(QRegularExpression::anchoredPattern( - uR"((?<compName>[a-zA-z0-9_]+)\.(?:qml|qmlannotation))")); - QRegularExpressionMatch m = qmlFileRe.match(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; diff --git a/src/qmldom/qqmldomexternalitems_p.h b/src/qmldom/qqmldomexternalitems_p.h index 8072d2fa8a..97328b40de 100644 --- a/src/qmldom/qqmldomexternalitems_p.h +++ b/src/qmldom/qqmldomexternalitems_p.h @@ -23,9 +23,12 @@ #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) @@ -46,14 +49,15 @@ 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, QString code = QString()); + ExternalOwningItem( + const QString &filePath, const QDateTime &lastDataUpdateAt, const Path &pathFromTop, + int derivedFrom = 0, const QString &code = QString()); ExternalOwningItem(const ExternalOwningItem &o) = default; - QString canonicalFilePath(DomItem &) const override; + QString canonicalFilePath(const DomItem &) const override; QString canonicalFilePath() const; - Path canonicalPath(DomItem &) const override; + Path canonicalPath(const DomItem &) const override; Path canonicalPath() const; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override { bool cont = OwningItem::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueLazyField(visitor, Fields::canonicalFilePath, [this]() { @@ -67,12 +71,12 @@ public: return cont; } - bool iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor) override + 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](QString, DomItem &comps) { - return comps.visitIndexes([visitor](DomItem &comp) { - return comp.field(Fields::objects).visitIndexes([visitor](DomItem &qmlObj) { + 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); @@ -104,7 +108,7 @@ protected: class QMLDOM_EXPORT QmlDirectory final : public ExternalOwningItem { protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { return std::make_shared<QmlDirectory>(*this); } @@ -112,23 +116,24 @@ protected: public: constexpr static DomType kindValue = DomType::QmlDirectory; DomType kind() const override { return kindValue; } - QmlDirectory(QString filePath = QString(), QStringList dirList = QStringList(), - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0); + 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(DomItem &self) const + std::shared_ptr<QmlDirectory> makeCopy(const DomItem &self) const { return std::static_pointer_cast<QmlDirectory>(doCopy(self)); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + 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(QString relativePath); + bool addQmlFilePath(const QString &relativePath); private: QMultiMap<QString, Export> m_exports; @@ -139,7 +144,7 @@ class QMLDOM_EXPORT QmldirFile final : public ExternalOwningItem { Q_DECLARE_TR_FUNCTIONS(QmldirFile) protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { auto copy = std::make_shared<QmldirFile>(*this); return copy; @@ -151,23 +156,24 @@ public: static ErrorGroups myParsingErrors(); - QmldirFile(QString filePath = QString(), QString code = QString(), - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0) + 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(QString path, QString code); + static std::shared_ptr<QmldirFile> fromPathAndCode(const QString &path, const QString &code); - std::shared_ptr<QmldirFile> makeCopy(DomItem &self) const + std::shared_ptr<QmldirFile> makeCopy(const DomItem &self) const { return std::static_pointer_cast<QmldirFile>(doCopy(self)); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; QmlUri uri() const { return m_uri; } @@ -188,7 +194,7 @@ public: QList<ModuleAutoExport> autoExports() const; void setAutoExports(const QList<ModuleAutoExport> &autoExport); - void ensureInModuleIndex(DomItem &self, QString uri); + void ensureInModuleIndex(const DomItem &self, const QString &uri) const; private: void parse(); @@ -207,7 +213,7 @@ private: class QMLDOM_EXPORT JsFile final : public ExternalOwningItem { protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { auto copy = std::make_shared<JsFile>(*this); return copy; @@ -216,129 +222,272 @@ protected: public: constexpr static DomType kindValue = DomType::JsFile; DomType kind() const override { return kindValue; } - JsFile(QString filePath = QString(), - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - Path pathFromTop = Path(), int derivedFrom = 0) + 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(DomItem &self) const + 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(DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; public: constexpr static DomType kindValue = DomType::QmlFile; DomType kind() const override { return kindValue; } - QmlFile(const QmlFile &o); - QmlFile(QString filePath = QString(), QString code = QString(), - QDateTime lastDataUpdate = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0); + 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(DomItem &self, DirectVisitor) + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; // iterates the *direct* subpaths, returns false if a quick end was requested - DomItem field(DomItem &self, QStringView name) const override - { - return const_cast<QmlFile *>(this)->field(self, name); - } - DomItem field(DomItem &self, QStringView name); - std::shared_ptr<QmlFile> makeCopy(DomItem &self) const + 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(DomItem &self, ErrorMessage msg) override; + void addError(const DomItem &self, ErrorMessage &&msg) override; - const QMultiMap<QString, QmlComponent> &components() const & { return m_components; } + const QMultiMap<QString, QmlComponent> &components() const & + { + return lazyMembers().m_components; + } void setComponents(const QMultiMap<QString, QmlComponent> &components) { - m_components = 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), m_components, key, - component, option, cPtr); + return insertUpdatableElementInMultiMap(Path::Field(Fields::components), lazyMembers().m_components, + key, component, option, cPtr); } - void writeOut(DomItem &self, OutWriter &lw) const override; + 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 m_imports; } - void setImports(const QList<Import> &imports) { m_imports = imports; } + const QList<Import> &imports() const & + { + return lazyMembers().m_imports; + } + void setImports(const QList<Import> &imports) { lazyMembers().m_imports = imports; } Path addImport(const Import &i) { - index_type idx = index_type(m_imports.size()); - m_imports.append(i); + auto &members = lazyMembers(); + index_type idx = index_type(members.m_imports.size()); + members.m_imports.append(i); if (i.uri.isModule()) { - m_importScope.addImport((i.importId.isEmpty() - ? QStringList() - : i.importId.split(QChar::fromLatin1('.'))), - i.importedPath()); + 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()) - m_importScope.addImport((i.importId.isEmpty() - ? QStringList() - : i.importId.split(QChar::fromLatin1('.'))), - Paths::qmlDirPath(path)); + 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 m_comments; } - std::shared_ptr<AstComments> astComments() const { return m_astComments; } - void setAstComments(std::shared_ptr<AstComments> comm) { m_astComments = comm; } - FileLocations::Tree fileLocationsTree() const { return m_fileLocationsTree; } - void setFileLocationsTree(FileLocations::Tree v) { m_fileLocationsTree = v; } - const QList<Pragma> &pragmas() const & { return m_pragmas; } - void setPragmas(QList<Pragma> pragmas) { m_pragmas = pragmas; } + 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) { - int idx = m_pragmas.size(); - m_pragmas.append(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 m_importScope; } - const ImportScope &importScope() const { return m_importScope; } + 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; } + + QQmlJSScope::ConstPtr handleForPopulation() const + { + return m_handleForPopulation; + } + + void setHandleForPopulation(const QQmlJSScope::ConstPtr &scope) + { + m_handleForPopulation = scope; + } + private: - friend class QmlDomAstCreator; - std::shared_ptr<Engine> m_engine; + // 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<AstComments> m_astComments; - RegionComments m_comments; - FileLocations::Tree m_fileLocationsTree; - QMultiMap<QString, QmlComponent> m_components; - QList<Pragma> m_pragmas; - QList<Import> m_imports; - ImportScope m_importScope; + 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(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { auto res = std::make_shared<QmltypesFile>(*this); return res; @@ -348,9 +497,10 @@ public: constexpr static DomType kindValue = DomType::QmltypesFile; DomType kind() const override { return kindValue; } - QmltypesFile(QString filePath = QString(), QString code = QString(), - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0) + 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) { @@ -358,10 +508,10 @@ public: QmltypesFile(const QmltypesFile &o) = default; - void ensureInModuleIndex(DomItem &self); + void ensureInModuleIndex(const DomItem &self) const; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - std::shared_ptr<QmltypesFile> makeCopy(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)); } @@ -392,7 +542,7 @@ public: } const QMap<QString, QSet<int>> &uris() const & { return m_uris; } - void addUri(QString uri, int majorVersion) + void addUri(const QString &uri, int majorVersion) { QSet<int> &v = m_uris[uri]; if (!v.contains(majorVersion)) { @@ -410,30 +560,31 @@ private: class QMLDOM_EXPORT GlobalScope final : public ExternalOwningItem { protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override; + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override; public: constexpr static DomType kindValue = DomType::GlobalScope; DomType kind() const override { return kindValue; } - GlobalScope(QString filePath = QString(), - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0) + 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(DomItem &self, DirectVisitor visitor) override; - std::shared_ptr<GlobalScope> makeCopy(DomItem &self) const + 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(QString name) { m_name = name; } + void setName(const QString &name) { m_name = name; } void setLanguage(Language language) { m_language = language; } void setRootComponent(const GlobalComponent &ob) { diff --git a/src/qmldom/qqmldomfieldfilter.cpp b/src/qmldom/qqmldomfieldfilter.cpp index 17529de4e9..67b33bbb7e 100644 --- a/src/qmldom/qqmldomfieldfilter.cpp +++ b/src/qmldom/qqmldomfieldfilter.cpp @@ -1,7 +1,9 @@ // 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 @@ -61,7 +63,7 @@ QString FieldFilter::describeFieldsFilter() const return fieldFilterStr; } -bool FieldFilter::operator()(DomItem &obj, Path p, DomItem &i) const +bool FieldFilter::operator()(const DomItem &obj, const Path &p, const DomItem &i) const { if (p) return this->operator()(obj, p.component(0), i); @@ -69,7 +71,7 @@ bool FieldFilter::operator()(DomItem &obj, Path p, DomItem &i) const return this->operator()(obj, PathEls::Empty(), i); } -bool FieldFilter::operator()(DomItem &base, const PathEls::PathComponent &c, DomItem &obj) const +bool FieldFilter::operator()(const DomItem &base, const PathEls::PathComponent &c, const DomItem &obj) const { DomType baseK = base.internalKind(); if (c.kind() == Path::Kind::Field) { @@ -95,7 +97,7 @@ bool FieldFilter::operator()(DomItem &base, const PathEls::PathComponent &c, Dom return true; } -bool FieldFilter::addFilter(QString fFields) +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 @@ -119,6 +121,11 @@ bool FieldFilter::addFilter(QString fFields) return true; } +FieldFilter FieldFilter::noFilter() +{ + return FieldFilter{ {}, {} }; +} + FieldFilter FieldFilter::defaultFilter() { QMultiMap<QString, QString> fieldFilterAdd { { QLatin1String("ScriptExpression"), diff --git a/src/qmldom/qqmldomfieldfilter_p.h b/src/qmldom/qqmldomfieldfilter_p.h index e40ce4459e..8ee5caa2c4 100644 --- a/src/qmldom/qqmldomfieldfilter_p.h +++ b/src/qmldom/qqmldomfieldfilter_p.h @@ -15,11 +15,13 @@ // We mean it. // +#include "qqmldom_fwd_p.h" #include "qqmldom_global.h" -#include "qqmldomitem_p.h" -#include "qqmldomastcreator_p.h" -#include "qqmldomcomments_p.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 @@ -32,9 +34,10 @@ class QMLDOM_EXPORT FieldFilter Q_GADGET public: QString describeFieldsFilter() const; - bool addFilter(QString f); - bool operator()(DomItem &, Path, DomItem &) const; - bool operator()(DomItem &, const PathEls::PathComponent &c, DomItem &) 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(); diff --git a/src/qmldom/qqmldomfilewriter.cpp b/src/qmldom/qqmldomfilewriter.cpp index f6fd650c13..82f2aca496 100644 --- a/src/qmldom/qqmldomfilewriter.cpp +++ b/src/qmldom/qqmldomfilewriter.cpp @@ -9,7 +9,7 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -FileWriter::Status FileWriter::write(QString tFile, function_ref<bool(QTextStream &)> write, +FileWriter::Status FileWriter::write(const QString &tFile, function_ref<bool(QTextStream &)> write, int nBk) { if (shouldRemoveTempFile) diff --git a/src/qmldom/qqmldomfilewriter_p.h b/src/qmldom/qqmldomfilewriter_p.h index 08a148ef5d..d9987f3f7a 100644 --- a/src/qmldom/qqmldomfilewriter_p.h +++ b/src/qmldom/qqmldomfilewriter_p.h @@ -37,14 +37,15 @@ public: ~FileWriter() { - if (!silentWarnings) - for (QString w : warnings) + if (!silentWarnings) { + for (const QString &w : std::as_const(warnings)) qWarning() << w; + } if (shouldRemoveTempFile) tempFile.remove(); } - Status write(QString targetFile, function_ref<bool(QTextStream &)> write, int nBk = 2); + Status write(const QString &targetFile, function_ref<bool(QTextStream &)> write, int nBk = 2); bool shouldRemoveTempFile = false; bool silentWarnings = false; diff --git a/src/qmldom/qqmldomfunctionref_p.h b/src/qmldom/qqmldomfunctionref_p.h index 65ef513ec2..525178e841 100644 --- a/src/qmldom/qqmldomfunctionref_p.h +++ b/src/qmldom/qqmldomfunctionref_p.h @@ -17,32 +17,44 @@ #include <QtCore/private/qglobal_p.h> -#include <functional> -// function_ref has been proposed for the C++20 standard, see -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0792r2.html -// uses it if available, replace it with a const ref to std::function otherwise - -#ifndef __cpp_lib_function_ref +#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 = const std::function<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 { -using std::function_ref; +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 // __cpp_lib_function_ref +#endif #endif // QQMLDOMFUNCTIONREF_P_H diff --git a/src/qmldom/qqmldomindentinglinewriter.cpp b/src/qmldom/qqmldomindentinglinewriter.cpp index 734a56943f..99f81c1717 100644 --- a/src/qmldom/qqmldomindentinglinewriter.cpp +++ b/src/qmldom/qqmldomindentinglinewriter.cpp @@ -24,7 +24,7 @@ void IndentingLineWriter::willCommit() m_preCachedStatus = fStatus().currentStatus; } -void IndentingLineWriter::reindentAndSplit(QString eol, bool eof) +void IndentingLineWriter::reindentAndSplit(const QString &eol, bool eof) { bool shouldReindent = m_reindent; indentAgain: diff --git a/src/qmldom/qqmldomindentinglinewriter_p.h b/src/qmldom/qqmldomindentinglinewriter_p.h index f066d370c9..63ec3fc432 100644 --- a/src/qmldom/qqmldomindentinglinewriter_p.h +++ b/src/qmldom/qqmldomindentinglinewriter_p.h @@ -31,7 +31,7 @@ QMLDOM_EXPORT class IndentingLineWriter : public LineWriter { Q_GADGET public: - IndentingLineWriter(SinkF innerSink, QString fileName, + 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, @@ -40,7 +40,7 @@ public: m_preCachedStatus(initialStatus) { } - void reindentAndSplit(QString eol, bool eof = false) override; + void reindentAndSplit(const QString &eol, bool eof = false) override; FormatPartialStatus &fStatus(); void lineChanged() override { m_fStatusValid = false; } diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp index e3f871acf0..8e4f8acd40 100644 --- a/src/qmldom/qqmldomitem.cpp +++ b/src/qmldom/qqmldomitem.cpp @@ -1,5 +1,9 @@ // 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" @@ -11,6 +15,8 @@ #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> @@ -32,6 +38,8 @@ #include <QtCore/QScopeGuard> #include <QtCore/QtGlobal> #include <QtCore/QTimeZone> +#include <optional> +#include <type_traits> QT_BEGIN_NAMESPACE @@ -39,7 +47,38 @@ namespace QQmlJS { namespace Dom { Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg); -static Q_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg); +Q_STATIC_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; /*! @@ -59,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 - Kind kind() const override { return kindValue; } // returns the kind of the current element - Path pathFromOwner(DomItem &self) const override; // returns the path from the owner to the -current element Path canonicalPath(DomItem &self) const override; // returns the path from virtual -bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem)>) const = 0; // iterates -the *direct* subpaths, returns false if a quick end was requested \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. + 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 +bool iterateDirectSubpaths(const DomItem &self, function_ref<bool(Path, DomItem)>) const = 0; +\endcode + +\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 */ @@ -170,20 +228,7 @@ bool domTypeIsScope(DomType k) } } -QCborValue locationToData(SourceLocation loc, QStringView strValue) -{ - QCborMap res({ - {QStringLiteral(u"offset"), loc.offset}, - {QStringLiteral(u"length"), loc.length}, - {QStringLiteral(u"startLine"), loc.startLine}, - {QStringLiteral(u"startColumn"), loc.startColumn} - }); - if (!strValue.isEmpty()) - res.insert(QStringLiteral(u"strValue"), QCborValue(strValue)); - return res; -} - -QString DomBase::canonicalFilePath(DomItem &self) const +QString DomBase::canonicalFilePath(const DomItem &self) const { auto parent = containingObject(self); if (parent) @@ -191,21 +236,21 @@ QString DomBase::canonicalFilePath(DomItem &self) const return QString(); } -void DomBase::writeOut(DomItem &self, OutWriter &) const +void DomBase::writeOut(const DomItem &self, OutWriter &) const { qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(kind()) << ":" << self.canonicalPath(); } -ConstantData::ConstantData(Path pathFromOwner, QCborValue value, 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, DirectVisitor visitor) +bool ConstantData::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { static QHash<QString, QString> knownFields; static QBasicMutex m; - auto toField = [](QString f) -> QStringView { + auto toField = [](const QString &f) -> QStringView { QMutexLocker l(&m); if (!knownFields.contains(f)) knownFields[f] = f; @@ -302,6 +347,41 @@ 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(); @@ -317,7 +397,7 @@ ErrorGroups DomItem::myResolveErrors() return res; } -Path DomItem::canonicalPath() +Path DomItem::canonicalPath() const { Path res = visitEl([this](auto &&el) { return el->canonicalPath(*this); }); if (!(!res || res.headKind() == Path::Kind::Root)) { @@ -328,14 +408,20 @@ Path DomItem::canonicalPath() } -DomItem DomItem::containingObject() +DomItem DomItem::containingObject() const { return visitEl([this](auto &&el) { return el->containingObject(*this); }); } -DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) +/*! + \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, DomItem &) { return k == DomType::QmlObject; }, + if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlObject; }, filterOptions)) return res; if (options == GoTo::MostLikely) { @@ -345,7 +431,7 @@ DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) return DomItem(); } -DomItem DomItem::fileObject(GoTo options) +DomItem DomItem::fileObject(GoTo options) const { DomItem res = *this; DomType k = res.internalKind(); @@ -368,19 +454,12 @@ DomItem DomItem::fileObject(GoTo options) return res; } -DomItem DomItem::rootQmlObject(GoTo options) +DomItem DomItem::rootQmlObject(GoTo options) const { - if (DomItem res = filterUp([](DomType k, DomItem &) { return k == DomType::QmlObject; }, - FilterUpOptions::ReturnInner)) - return res; - if (options == GoTo::MostLikely) { - if (DomItem comp = component(options)) - return comp.field(Fields::objects).index(0); - } - return DomItem(); + return qmlObject(options, FilterUpOptions::ReturnInner); } -DomItem DomItem::container() +DomItem DomItem::container() const { Path path = pathFromOwner(); if (!path) @@ -391,7 +470,7 @@ DomItem DomItem::container() return containingObject(); } -DomItem DomItem::globalScope() +DomItem DomItem::globalScope() const { if (internalKind() == DomType::GlobalScope) return *this; @@ -403,23 +482,35 @@ DomItem DomItem::globalScope() return DomItem(); } -DomItem DomItem::owner() +/*! + \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) { return DomItem(this->m_top, el, this->m_ownerPath, el.get()); }, - *m_owner); + 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() +DomItem DomItem::top() const { if (domTypeIsTopItem(m_kind) || m_kind == DomType::Empty) return *this; - return std::visit([](auto &&el) { return DomItem(el, el, Path(), el.get()); }, *m_top); + 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() +DomItem DomItem::environment() const { DomItem res = top(); if (res.internalKind() == DomType::DomEnvironment) @@ -427,7 +518,7 @@ DomItem DomItem::environment() return DomItem(); // we are in the universe, and cannot go back to the environment... } -DomItem DomItem::universe() +DomItem DomItem::universe() const { DomItem res = top(); if (res.internalKind() == DomType::DomUniverse) @@ -437,91 +528,169 @@ DomItem DomItem::universe() return DomItem(); // we should be in an empty DomItem already... } -DomItem DomItem::filterUp(function_ref<bool(DomType k, DomItem &)> filter, FilterUpOptions options) +/*! + \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 +{ + 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; +} + +/*! + \internal + In the DomItem hierarchy, go 1 level up to get the direct parent. + */ +DomItem DomItem::directParent() const { - DomItem it = *this; - DomType k = it.internalKind(); + return goUp(1); +} + +/*! +\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 +{ + if (options == FilterUpOptions::ReturnOuter && filter(internalKind(), *this)) { + return *this; + } + switch (options) { case FilterUpOptions::ReturnOuter: case FilterUpOptions::ReturnOuterNoSelf: { - bool checkTop = (options == FilterUpOptions::ReturnOuter); - while (k != DomType::Empty) { - if (checkTop && filter(k, it)) - return it; - checkTop = true; - if (!domTypeIsOwningItem(k)) { - DomItem el = it.owner(); - DomItem res; - k = DomType::Empty; - Path pp = it.pathFromOwner(); - DomType k2 = el.internalKind(); - if (filter(k2, el)) { - k = k2; - res = el; - } - for (Path p : pp.mid(0, pp.length() - 1)) { - el = el.path(p); - DomType k2 = el.internalKind(); - if (filter(k2, el)) { - k = k2; - res = el; - } - } - if (k != DomType::Empty) - return res; - it = it.owner(); + 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; } - it = it.containingObject(); - k = it.internalKind(); } - } break; + break; + } case FilterUpOptions::ReturnInner: - while (k != DomType::Empty) { - if (!domTypeIsOwningItem(k)) { - DomItem el = owner(); - Path pp = pathFromOwner(); - for (Path p : pp) { - DomItem child = el.path(p); - DomType k2 = child.internalKind(); - if (filter(k2, child)) - return child; - el = child; - } - it = it.owner(); - } - it = it.containingObject(); - k = it.internalKind(); + 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) +DomItem DomItem::scope(FilterUpOptions options) const { - DomItem res = filterUp([](DomType, DomItem &el) { return el.isScope(); }, options); + DomItem res = filterUp([](DomType, const DomItem &el) { return el.isScope(); }, options); return res; } -DomItem DomItem::get(ErrorHandler h, QList<Path> *visitedRefs) +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(ErrorHandler h, QList<Path> *visitedRefs) +QList<DomItem> DomItem::getAll(const ErrorHandler &h, QList<Path> *visitedRefs) const { if (const Reference *refPtr = as<Reference>()) return refPtr->getAll(*this, h, visitedRefs); return {}; } -PropertyInfo DomItem::propertyInfoWithName(QString name) +PropertyInfo DomItem::propertyInfoWithName(const QString &name) const { PropertyInfo pInfo; - visitPrototypeChain([&pInfo, name](DomItem &obj) { - return obj.visitLocalSymbolsNamed(name, [&pInfo, name](DomItem &el) { + 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); @@ -538,10 +707,10 @@ PropertyInfo DomItem::propertyInfoWithName(QString name) return pInfo; } -QSet<QString> DomItem::propertyInfoNames() +QSet<QString> DomItem::propertyInfoNames() const { QSet<QString> res; - visitPrototypeChain([&res](DomItem &obj) { + visitPrototypeChain([&res](const DomItem &obj) { res += obj.propertyDefs().keys(); res += obj.bindings().keys(); return true; @@ -549,10 +718,10 @@ QSet<QString> DomItem::propertyInfoNames() return res; } -DomItem DomItem::component(GoTo options) +DomItem DomItem::component(GoTo options) const { if (DomItem res = filterUp( - [](DomType kind, DomItem &) { + [](DomType kind, const DomItem &) { return kind == DomType::QmlComponent || kind == DomType::QmltypesComponent || kind == DomType::GlobalComponent; }, @@ -597,8 +766,8 @@ static QMap<LookupType, QString> lookupTypeToStringMap() return map; } -bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHandler, - ResolveOptions options, Path fullPath, QList<Path> *visitedRefs) +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; @@ -635,13 +804,12 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan 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.id(); if (idNow == quintptr(0) && toDo.item == *this) @@ -678,7 +846,7 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan } if (visitedRefs->contains(refRef)) { myResolveErrors() - .error([visitedRefs, refRef](Sink sink) { + .error([visitedRefs, refRef](const Sink &sink) { const QString msg = tr("Circular reference:") + QLatin1Char('\n'); sink(QStringView{msg}); for (const Path &vPath : *visitedRefs) { @@ -695,7 +863,7 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan DomItem resolveRes; it.resolve( toResolve, - [&resolveRes](Path, DomItem &r) { + [&resolveRes](Path, const DomItem &r) { resolveRes = r; return false; }, @@ -732,11 +900,11 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan if (!branchExhausted) visitTree( Path(), - [toFind, &toDos, iPath](Path, DomItem &item, bool) { + [&toFind, &toDos, iPath](Path, const DomItem &item, bool) { // avoid non directly attached? DomItem newItem = item[toFind]; if (newItem) - toDos.append({ newItem, iPath }); + toDos.append({ std::move(newItem), iPath }); return true; }, VisitOption::VisitSelf | VisitOption::Recurse @@ -760,7 +928,7 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan break; case PathCurrent::ObjChain: { bool cont = it.visitPrototypeChain( - [&toDos, iPath](DomItem &subEl) { + [&toDos, iPath](const DomItem &subEl) { toDos.append({ subEl, iPath }); return true; }, @@ -773,7 +941,7 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan } case PathCurrent::ScopeChain: { bool cont = it.visitScopeChain( - [&toDos, iPath](DomItem &subEl) { + [&toDos, iPath](const DomItem &subEl) { toDos.append({ subEl, iPath }); return true; }, @@ -903,7 +1071,7 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan } it.visitLookup( target, - [&toDos, iPath](DomItem &subEl) { + [&toDos, iPath](const DomItem &subEl) { toDos.append({ subEl, iPath }); return true; }, @@ -917,7 +1085,7 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan case Path::Kind::Any: visitTree( Path(), - [&toDos, iPath](Path, DomItem &item, bool) { + [&toDos, iPath](Path, const DomItem &item, bool) { toDos.append({ item, iPath }); return true; }, @@ -937,49 +1105,49 @@ bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHan return true; } -DomItem DomItem::path(Path p, ErrorHandler errorHandler) +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) +DomItem DomItem::path(const QString &p, const ErrorHandler &errorHandler) const { return path(Path::fromString(p, errorHandler)); } -DomItem DomItem::path(QStringView p, ErrorHandler errorHandler) +DomItem DomItem::path(QStringView p, const ErrorHandler &errorHandler) const { return path(Path::fromString(p, errorHandler)); } -QList<QString> DomItem::fields() +QList<QString> DomItem::fields() const { return visitEl([this](auto &&el) { return el->fields(*this); }); } -DomItem DomItem::field(QStringView name) +DomItem DomItem::field(QStringView name) const { return visitEl([this, name](auto &&el) { return el->field(*this, name); }); } -index_type DomItem::indexes() +index_type DomItem::indexes() const { return visitEl([this](auto &&el) { return el->indexes(*this); }); } -DomItem DomItem::index(index_type i) +DomItem DomItem::index(index_type i) const { return visitEl([this, i](auto &&el) { return el->index(*this, i); }); } -bool DomItem::visitIndexes(function_ref<bool(DomItem &)> visitor) +bool DomItem::visitIndexes(function_ref<bool(const DomItem &)> visitor) const { // use iterateDirectSubpathsConst instead? int nIndexes = indexes(); @@ -991,12 +1159,12 @@ bool DomItem::visitIndexes(function_ref<bool(DomItem &)> visitor) return true; } -QSet<QString> DomItem::keys() +QSet<QString> DomItem::keys() const { return visitEl([this](auto &&el) { return el->keys(*this); }); } -QStringList DomItem::sortedKeys() +QStringList DomItem::sortedKeys() const { QSet<QString> ks = keys(); QStringList sortedKs(ks.begin(), ks.end()); @@ -1004,15 +1172,16 @@ QStringList DomItem::sortedKeys() return sortedKs; } -DomItem DomItem::key(QString name) +DomItem DomItem::key(const QString &name) const { return visitEl([this, name](auto &&el) { return el->key(*this, name); }); } -bool DomItem::visitKeys(function_ref<bool(QString, DomItem &)> visitor) +bool DomItem::visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const { // use iterateDirectSubpathsConst instead? - for (auto k : sortedKeys()) { + const QStringList keys = sortedKeys(); + for (const QString &k : keys) { DomItem v = key(k); if (!visitor(k, v)) return false; @@ -1020,10 +1189,10 @@ bool DomItem::visitKeys(function_ref<bool(QString, DomItem &)> visitor) return true; } -QList<DomItem> DomItem::values() +QList<DomItem> DomItem::values() const { QList<DomItem> res; - visitEl([this, &res](auto &&el) { + visitEl([this, &res](const auto &el) { return el->iterateDirectSubpathsConst( *this, [&res](const PathEls::PathComponent &, function_ref<DomItem()> item) { res.append(item()); @@ -1033,11 +1202,11 @@ QList<DomItem> DomItem::values() return res; } -void DomItem::writeOutPre(OutWriter &ow) +void DomItem::writeOutPre(OutWriter &ow) const { if (hasAnnotations()) { DomItem anns = field(Fields::annotations); - for (auto ann : anns.values()) { + for (const auto &ann : anns.values()) { if (ann.annotations().indexes() == 0) { ow.ensureNewline(); ann.writeOut(ow); @@ -1052,172 +1221,198 @@ void DomItem::writeOutPre(OutWriter &ow) ow.itemStart(*this); } -void DomItem::writeOut(OutWriter &ow) +void DomItem::writeOut(OutWriter &ow) const { writeOutPre(ow); visitEl([this, &ow](auto &&el) { el->writeOut(*this, ow); }); writeOutPost(ow); } -void DomItem::writeOutPost(OutWriter &ow) +void DomItem::writeOutPost(OutWriter &ow) const { ow.itemEnd(*this); } -DomItem DomItem::writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) +DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &original, const DomItem &reformatted, + OutWriter &ow, + WriteOutChecks extraChecks) const +{ + 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; +} + +/*! + \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(); - DomItem fObj = fileObject(); - DomItem copy = ow.updatedFile(fObj); - if (extraChecks & WriteOutCheck::All) { - QStringList dumped; - auto maybeDump = [&ow, extraChecks, &dumped](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](Sink s) { - if (dumped.isEmpty()) - return; - s(u"\ndump: "); - for (auto dumpPath : dumped) { - s(u" "); - sinkEscaped(s, dumpPath); - } - }; - auto compare = [&maybeDump, &dumpedDumper, this](DomItem &obj1, QStringView obj1Name, - DomItem &obj2, QStringView obj2Name, - const FieldFilter &filter) { - if (!domCompareStrList(obj1, obj2, filter).isEmpty()) { - maybeDump(obj1, obj1Name); - maybeDump(obj2, obj2Name); - qCWarning(writeOutLog).noquote().nospace() - << obj2Name << " writeOut of " << this->canonicalFilePath() - << " has changes:\n" - << domCompareStrList(obj1, obj2, filter, DomCompareStrList::AllDiffs) - .join(QString()) - << dumpedDumper; - return false; - } - return true; - }; - auto checkStability = [&maybeDump, &dumpedDumper, &dumped, &ow, - this](QString expected, 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(fObj, u"initial", copy, u"reformatted", FieldFilter::noLocationFilter())) - return DomItem(); - if (extraChecks & WriteOutCheck::UpdatedDomStable) - checkStability(ow.writtenStr, copy, u"reformatted"); - if (extraChecks - & (WriteOutCheck::Reparse | WriteOutCheck::ReparseCompare - | WriteOutCheck::ReparseStable)) { - DomItem newEnv = environment().makeCopy().item(); - if (std::shared_ptr<DomEnvironment> newEnvPtr = newEnv.ownerAs<DomEnvironment>()) { - auto newFilePtr = std::make_shared<QmlFile>( - canonicalFilePath(), ow.writtenStr); - newEnvPtr->addQmlFile(newFilePtr, AddOption::Overwrite); - DomItem newFile = newEnv.copy(newFilePtr, Path()); - if (newFilePtr->isValid()) { - if (extraChecks - & (WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) { - MutableDomItem newFileMutable(newFile); - createDom(newFileMutable); - if ((extraChecks & WriteOutCheck::ReparseCompare) - && !compare(copy, u"reformatted", newFile, u"reparsed", - FieldFilter::compareNoCommentsFilter())) - return DomItem(); - if ((extraChecks & WriteOutCheck::ReparseStable)) - checkStability(ow.writtenStr, newFile, u"reparsed"); - } - } else { - qCWarning(writeOutLog).noquote().nospace() - << "writeOut of " << canonicalFilePath() - << " created invalid code:\n----------\n" - << ow.writtenStr << "\n----------" << [&newFile](Sink s) { - newFile.iterateErrors( - [s](DomItem, ErrorMessage msg) { - s(u"\n "); - msg.dump(s); - return true; - }, - true); - s(u"\n"); // extra empty line at the end... - }; - return DomItem(); - } - } - } - } - return copy; + + 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; } -DomItem DomItem::writeOut(QString path, int nBackups, const LineWriterOptions &options, - FileWriter *fw, WriteOutChecks extraChecks) +bool DomItem::writeOut(const QString &path, int nBackups, const LineWriterOptions &options, + FileWriter *fw, WriteOutChecks extraChecks) const { - DomItem res = *this; - DomItem copy; FileWriter localFw; if (!fw) fw = &localFw; - switch (fw->write( + auto status = fw->write( path, - [this, path, ©, &options, extraChecks](QTextStream &ts) { + [this, path, &options, extraChecks](QTextStream &ts) { LineWriter lw([&ts](QStringView s) { ts << s; }, path, options); OutWriter ow(lw); - copy = writeOutForFile(ow, extraChecks); - return bool(copy); + return writeOutForFile(ow, extraChecks); }, - nBackups)) { + 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; - break; - case FileWriter::Status::DidWrite: - case FileWriter::Status::SkippedEqual: - res = copy; - break; + return false; + default: + qCWarning(writeOutLog) << "Unknown FileWriter::Status "; + Q_ASSERT(false); + return false; } - return res; } -bool DomItem::isCanonicalChild(DomItem &item) +bool DomItem::isCanonicalChild(const DomItem &item) const { + bool isChild = false; if (item.isOwningItem()) { - return canonicalPath() == item.canonicalPath().dropTail(); + isChild = canonicalPath() == item.canonicalPath().dropTail(); } else { DomItem itemOw = item.owner(); DomItem selfOw = owner(); - return itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner(); + isChild = itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner(); } + return isChild; } -bool DomItem::hasAnnotations() +bool DomItem::hasAnnotations() const { bool hasAnnotations = false; DomType iKind = internalKind(); @@ -1248,28 +1443,56 @@ bool DomItem::hasAnnotations() return hasAnnotations; } -bool DomItem::visitTree(Path basePath, DomItem::ChildrenVisitor visitor, VisitOptions options, - DomItem::ChildrenVisitor openingVisitor, - DomItem::ChildrenVisitor closingVisitor) +/*! + \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 (!openingVisitor(basePath, *this, true)) + if (options & VisitOption::VisitSelf && !openingVisitor(basePath, *this, true)) return true; - auto atEnd = qScopeGuard( - [closingVisitor, basePath, this]() { closingVisitor(basePath, *this, true); }); - return visitEl([this, basePath, visitor, openingVisitor, closingVisitor, options](auto &&el) { + 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](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) { + [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)) @@ -1285,16 +1508,81 @@ bool DomItem::visitTree(Path basePath, DomItem::ChildrenVisitor visitor, VisitOp closingVisitor(pNow, item, directChild); } else { return item.visitTree(pNow, visitor, options | VisitOption::VisitSelf, - openingVisitor, closingVisitor); + 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) +{ + Path elId = prototype.canonicalPath(); + if (visitedRefs->contains(elId)) + return true; + else + 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; +} -bool DomItem::visitPrototypeChain(function_ref<bool(DomItem &)> visitor, - VisitPrototypesOptions options, ErrorHandler h, - QSet<quintptr> *visited, QList<Path> *visitedRefs) +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) @@ -1330,72 +1618,16 @@ bool DomItem::visitPrototypeChain(function_ref<bool(DomItem &)> visitor, return false; shouldVisit = true; current.field(Fields::prototypes) - .visitIndexes([&toDo, ¤t, this, &h, visitedRefs, options](DomItem &el) { - Path elId = el.canonicalPath(); - if (visitedRefs->contains(elId)) - return true; - else - visitedRefs->append(elId); - QList<DomItem> protos = el.getAll(h, visitedRefs); - if (protos.isEmpty()) { - if (std::shared_ptr<DomEnvironment> envPtr = - environment().ownerAs<DomEnvironment>()) - if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) - myErrors() - .warning(tr("could not resolve prototype %1 (%2)") - .arg(current.canonicalPath().toString(), - el.field(Fields::referredObjectPath) - .value() - .toString())) - .withItem(*this) - .handle(h); - } else { - if (protos.size() > 1) { - QStringList protoPaths; - for (DomItem &p : protos) - protoPaths.append(p.canonicalPath().toString()); - myErrors() - .warning(tr("Multiple definitions found, using first only, " - "resolving prototype %1 (%2): %3") - .arg(current.canonicalPath().toString(), - el.field(Fields::referredObjectPath) - .value() - .toString(), - protoPaths.join(QLatin1String(", ")))) - .withItem(*this) - .handle(h); - } - int nProtos = 1; // change to protos.length() to us all prototypes found - // (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) { - toDo.append(proto); - } else { - myErrors() - .warning(tr("Unexpected prototype type %1 (%2)") - .arg(current.canonicalPath().toString(), - el.field(Fields::referredObjectPath) - .value() - .toString())) - .withItem(*this) - .handle(h); - } - } - } - return true; + .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(DomItem &)> visitor, - VisitPrototypesOptions options, ErrorHandler h, - QSet<quintptr> *visited, QList<Path> *visitedRefs) +bool DomItem::visitDirectAccessibleScopes( + function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options, + const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const { // these are the scopes one can access with the . operator from the current location // but currently not the attached types, which we should @@ -1440,9 +1672,9 @@ bool DomItem::visitDirectAccessibleScopes(function_ref<bool(DomItem &)> visitor, * 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(DomItem &)> visitor, - VisitPrototypesOptions options, ErrorHandler h, - QSet<quintptr> *visited, QList<Path> *visitedRefs) +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) @@ -1460,8 +1692,27 @@ bool DomItem::visitStaticTypePrototypeChains(function_ref<bool(DomItem &)> visit return true; } -bool DomItem::visitScopeChain(function_ref<bool(DomItem &)> visitor, LookupOptions options, - ErrorHandler h, QSet<quintptr> *visited, QList<Path> *visitedRefs) +/*! + \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; +} + +/*! + \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) @@ -1478,6 +1729,7 @@ bool DomItem::visitScopeChain(function_ref<bool(DomItem &)> visitor, LookupOptio 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())) @@ -1505,7 +1757,7 @@ bool DomItem::visitScopeChain(function_ref<bool(DomItem &)> visitor, LookupOptio if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) toDo.append(next); break; - case DomType::QmlComponent: // ids/attached type + case DomType::QmlComponent: { // ids/attached type if ((options & LookupOption::Strict) == 0) { if (DomItem comp = current.field(Fields::nextComponent)) toDo.append(comp); @@ -1517,8 +1769,25 @@ bool DomItem::visitScopeChain(function_ref<bool(DomItem &)> visitor, LookupOptio } 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? @@ -1558,118 +1827,14 @@ bool DomItem::visitScopeChain(function_ref<bool(DomItem &)> visitor, LookupOptio return true; } -QSet<QString> DomItem::localSymbolNames(LocalSymbolsTypes typeFilter) +bool DomItem::visitLookup1( + const QString &symbolName, function_ref<bool(const DomItem &)> visitor, LookupOptions opts, + const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const { - QSet<QString> res; - if (typeFilter == LocalSymbolsType::None) - return res; - switch (internalKind()) { - case DomType::QmlObject: - if (typeFilter & LocalSymbolsType::Attributes) { - res += propertyDefs().keys(); - res += bindings().keys(); - } - if (typeFilter & LocalSymbolsType::Methods) { - if ((typeFilter & LocalSymbolsType::Methods) == LocalSymbolsType::Methods) { - res += methods().keys(); - } else { - bool shouldAddSignals = bool(typeFilter & LocalSymbolsType::Signals); - if (const QmlObject *objPtr = as<QmlObject>()) { - auto methods = objPtr->methods(); - for (auto it = methods.cbegin(); it != methods.cend(); ++it) { - if (bool(it.value().methodType == MethodInfo::MethodType::Signal) - == shouldAddSignals) - res += it.key(); - } - } - } - } - break; - case DomType::ScriptExpression: - // to do - break; - case DomType::QmlComponent: - if (typeFilter & LocalSymbolsType::Ids) - res += ids().keys(); - break; - case DomType::QmlFile: // subComponents, imported types - if (typeFilter & LocalSymbolsType::Components) { - DomItem comps = field(Fields::components); - for (auto k : comps.keys()) - if (!k.isEmpty()) - res.insert(k); - } - break; - case DomType::ImportScope: { - const ImportScope *currentPtr = as<ImportScope>(); - if (typeFilter & LocalSymbolsType::Types) { - if ((typeFilter & LocalSymbolsType::Types) == LocalSymbolsType::Types) { - res += currentPtr->importedNames(*this); - } else { - bool qmlTypes = bool(typeFilter & LocalSymbolsType::QmlTypes); - for (const QString &typeName : currentPtr->importedNames(*this)) { - if ((!typeName.isEmpty() && typeName.at(0).isUpper()) == qmlTypes) - res += typeName; - } - } - } - if (typeFilter & LocalSymbolsType::Namespaces) { - for (const auto &k : currentPtr->subImports().keys()) - res.insert(k); - } - break; - } - case DomType::QmltypesComponent: - case DomType::JsResource: - case DomType::GlobalComponent: - if (typeFilter & LocalSymbolsType::Globals) - res += enumerations().keys(); - break; - case DomType::MethodInfo: { - if (typeFilter & LocalSymbolsType::MethodParameters) { - DomItem params = field(Fields::parameters); - params.visitIndexes([&res](DomItem &p) { - const MethodParameter *pPtr = p.as<MethodParameter>(); - res.insert(pPtr->name); - return true; - }); - } - break; - } - default: - break; - } - return res; -} - -bool DomItem::visitLookup1(QString symbolName, function_ref<bool(DomItem &)> visitor, - LookupOptions opts, ErrorHandler h, QSet<quintptr> *visited, - QList<Path> *visitedRefs) -{ - bool typeLookupInQmlFile = symbolName.size() > 1 && symbolName.at(0).isUpper() - && fileObject().internalKind() == DomType::QmlFile; - if (typeLookupInQmlFile) { - // shortcut to lookup types (scope chain would find them too, but after looking - // the prototype chain) - DomItem importScope = fileObject().field(Fields::importScope); - if (const ImportScope *importScopePtr = importScope.as<ImportScope>()) { - if (importScopePtr->subImports().contains(symbolName)) { - DomItem subItem = importScope.field(Fields::qualifiedImports).key(symbolName); - if (!visitor(subItem)) - return false; - } - QList<DomItem> types = importScopePtr->importedItemsWithName(importScope, symbolName); - for (DomItem &t : types) { - if (!visitor(t)) - return false; - } - } - return true; - } return visitScopeChain( - [symbolName, visitor](DomItem &obj) { + [symbolName, visitor](const DomItem &obj) { return obj.visitLocalSymbolsNamed(symbolName, - [visitor](DomItem &el) { return visitor(el); }); + [visitor](const DomItem &el) { return visitor(el); }); }, opts, h, visited, visitedRefs); } @@ -1680,7 +1845,7 @@ class CppTypeInfo public: CppTypeInfo() = default; - static CppTypeInfo fromString(QStringView target, ErrorHandler h = nullptr) + static CppTypeInfo fromString(QStringView target, const ErrorHandler &h = nullptr) { CppTypeInfo res; QRegularExpression reTarget = QRegularExpression(QRegularExpression::anchoredPattern( @@ -1719,9 +1884,95 @@ public: bool isList = false; }; -bool DomItem::visitLookup(QString target, function_ref<bool(DomItem &)> visitor, - LookupType lookupType, LookupOptions opts, ErrorHandler errorHandler, - QSet<quintptr> *visited, QList<Path> *visitedRefs) +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; +} + +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 (target.isEmpty()) return true; @@ -1738,101 +1989,10 @@ bool DomItem::visitLookup(QString target, function_ref<bool(DomItem &)> visitor, } else { return visitLookup1( subpath.at(0), - [&subpath, visitor, lookupType, &errorHandler, visitedRefs](DomItem &newIt) { - 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](DomItem &el) { - return el.visitLocalSymbolsNamed( - subPathNow, - [&lookupToDos, iSubPath](DomItem &subEl) { - lookupToDos.append({ subEl, iSubPath }); - return true; - }); - }, - VisitPrototypesOption::Normal, errorHandler, - &(lookupVisited[vNow.second]), visitedRefs); - } else { - bool cont = scope.visitDirectAccessibleScopes( - [&visitor, subPathNow, lookupType](DomItem &el) -> bool { - if (lookupType == LookupType::Symbol) - return el.visitLocalSymbolsNamed(subPathNow, - visitor); - else - return el.visitLocalSymbolsNamed( - subPathNow, - [lookupType, - &visitor](DomItem &el) -> bool { - 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; - }); - }, - VisitPrototypesOption::Normal, errorHandler, - &(lookupVisited[vNow.second]), visitedRefs); - if (!cont) - return false; - } - } - return true; + [&subpath, visitor, lookupType, &errorHandler, + visitedRefs](const DomItem &newIt) -> bool { + return visitQualifiedNameLookup(newIt, subpath, visitor, lookupType, + errorHandler, visitedRefs); }, opts, errorHandler, visited, visitedRefs); } @@ -1847,8 +2007,8 @@ bool DomItem::visitLookup(QString target, function_ref<bool(DomItem &)> visitor, } if (localQmltypes) { if (DomItem localTypes = localQmltypes.field(Fields::components).key(baseTarget)) { - bool cont = localTypes.visitIndexes([&visitor](DomItem &els) { - return els.visitIndexes([&visitor](DomItem &el) { + 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; @@ -1859,10 +2019,10 @@ bool DomItem::visitLookup(QString target, function_ref<bool(DomItem &)> visitor, } } DomItem qmltypes = environment().field(Fields::qmltypesFileWithPath); - return qmltypes.visitKeys([baseTarget, &visitor](QString, DomItem &els) { + 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](DomItem &el) { + return comps.visitIndexes([&visitor](const DomItem &el) { if (DomItem obj = el.field(Fields::objects).index(0)) return visitor(obj); return true; @@ -1875,7 +2035,15 @@ bool DomItem::visitLookup(QString target, function_ref<bool(DomItem &)> visitor, return true; } -DomItem DomItem::proceedToScope(ErrorHandler h, QList<Path> *visitedRefs) +/*! + \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; @@ -1900,13 +2068,13 @@ DomItem DomItem::proceedToScope(ErrorHandler h, QList<Path> *visitedRefs) return DomItem(); } -QList<DomItem> DomItem::lookup(QString symbolName, LookupType type, LookupOptions opts, - ErrorHandler errorHandler) +QList<DomItem> DomItem::lookup(const QString &symbolName, LookupType type, LookupOptions opts, + const ErrorHandler &errorHandler) const { QList<DomItem> res; visitLookup( symbolName, - [&res](DomItem &el) { + [&res](const DomItem &el) { res.append(el); return true; }, @@ -1914,13 +2082,13 @@ QList<DomItem> DomItem::lookup(QString symbolName, LookupType type, LookupOption return res; } -DomItem DomItem::lookupFirst(QString symbolName, LookupType type, LookupOptions opts, - ErrorHandler errorHandler) +DomItem DomItem::lookupFirst(const QString &symbolName, LookupType type, LookupOptions opts, + const ErrorHandler &errorHandler) const { DomItem res; visitLookup( symbolName, - [&res](DomItem &el) { + [&res](const DomItem &el) { res = el; return false; }, @@ -1928,75 +2096,81 @@ DomItem DomItem::lookupFirst(QString symbolName, LookupType type, LookupOptions return res; } -quintptr DomItem::id() +quintptr DomItem::id() const { return visitEl([](auto &&b) { return b->id(); }); } -Path DomItem::pathFromOwner() +Path DomItem::pathFromOwner() const { return visitEl([this](auto &&e) { return e->pathFromOwner(*this); }); } -QString DomItem::canonicalFilePath() +QString DomItem::canonicalFilePath() const { return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); }); } -DomItem DomItem::fileLocationsTree() +DomItem DomItem::fileLocationsTree() const { if (DomItem l = field(Fields::fileLocationsTree)) return l; - auto res = FileLocations::findAttachedInfo(*this, AttachedInfo::FindOption::SetFoundTreePath); - if (res && res.foundTreePath.value()) { - return copy(res.foundTree, res.foundTreePath.value()); + auto res = FileLocations::findAttachedInfo(*this); + if (res && res.foundTreePath) { + return copy(res.foundTree, res.foundTreePath); } return DomItem(); } -DomItem DomItem::fileLocations() +DomItem DomItem::fileLocations() const { return fileLocationsTree().field(Fields::infoItem); } -MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) +MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) const { if (m_kind == DomType::Empty) return MutableDomItem(); DomItem o = owner(); if (option == CopyOption::EnvDisconnected) { - DomItem newItem = std::visit( - [this, &o](auto &&el) { - auto copyPtr = el->makeCopy(o); - return DomItem(m_top, copyPtr, m_ownerPath, copyPtr.get()); - }, - *m_owner); + 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()); + newEnvPtr = std::make_shared<DomEnvironment>(envPtr, envPtr->loadPaths(), envPtr->options(), + envPtr->domCreationOptions()); DomBase *eBase = envPtr.get(); - if (std::holds_alternative<DomEnvironment *>(m_element) && eBase - && std::get<DomEnvironment *>(m_element) == eBase) + 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, - univPtr); + DomCreationOption::None, univPtr); } else { Q_ASSERT(false); return {}; } DomItem newItem = std::visit( [this, newEnvPtr, &o](auto &&el) { - auto copyPtr = el->makeCopy(o); - return DomItem(newEnvPtr, copyPtr, m_ownerPath, copyPtr.get()); + 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); + m_owner); switch (o.internalKind()) { case DomType::QmlDirectory: @@ -2037,7 +2211,7 @@ MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) return MutableDomItem(newItem.path(pathFromOwner())); } -bool DomItem::commitToBase(std::shared_ptr<DomEnvironment> validEnvPtr) +bool DomItem::commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr) const { DomItem env = environment(); if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { @@ -2046,7 +2220,7 @@ bool DomItem::commitToBase(std::shared_ptr<DomEnvironment> validEnvPtr) return false; } -bool DomItem::visitLocalSymbolsNamed(QString name, function_ref<bool(DomItem &)> visitor) +bool DomItem::visitLocalSymbolsNamed(const QString &name, function_ref<bool(const DomItem &)> visitor) const { if (name.isEmpty()) // no empty symbol return true; @@ -2089,7 +2263,7 @@ bool DomItem::visitLocalSymbolsNamed(QString name, function_ref<bool(DomItem &)> break; case DomType::MethodInfo: { DomItem params = field(Fields::parameters); - if (!params.visitIndexes([name, visitor](DomItem &p) { + if (!params.visitIndexes([name, visitor](const DomItem &p) { const MethodParameter *pPtr = p.as<MethodParameter>(); if (pPtr->name == name && !visitor(p)) return false; @@ -2123,33 +2297,31 @@ bool DomItem::visitLocalSymbolsNamed(QString name, function_ref<bool(DomItem &)> return true; } -DomItem DomItem::operator[](const QString &cName) +DomItem DomItem::operator[](const QString &cName) const { if (internalKind() == DomType::Map) return key(cName); return field(cName); } -DomItem DomItem::operator[](QStringView cName) +DomItem DomItem::operator[](QStringView cName) const { if (internalKind() == DomType::Map) return key(cName.toString()); return field(cName); } -DomItem DomItem::operator[](Path p) +DomItem DomItem::operator[](const Path &p) const { return path(p); } -QCborValue DomItem::value() +QCborValue DomItem::value() const { - if (internalKind() == DomType::ConstantData) - return std::get<ConstantData>(m_element).value(); - return QCborValue(); + return base()->value(); } -void DomItem::dumpPtr(Sink sink) +void DomItem::dumpPtr(const Sink &sink) const { sink(u"DomItem{ topPtr:"); sink(QString::number((quintptr)topPtr().get(), 16)); @@ -2162,16 +2334,17 @@ void DomItem::dumpPtr(Sink sink) sink(u"}"); } -void DomItem::dump(Sink s, int indent, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) +void DomItem::dump( + const Sink &s, int indent, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const { visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); }); } FileWriter::Status -DomItem::dump(QString path, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter, - int nBackups, int indent, FileWriter *fw) +DomItem::dump(const QString &path, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter, + int nBackups, int indent, FileWriter *fw) const { FileWriter localFw; if (!fw) @@ -2194,122 +2367,140 @@ DomItem::dump(QString path, return fw->status; } -QString DomItem::toString() +QString DomItem::toString() const { - return dumperToString([this](Sink s){ dump(s); }); + return dumperToString([this](const Sink &s){ dump(s); }); } -int DomItem::derivedFrom() +int DomItem::derivedFrom() const { - if (m_owner) - return std::visit([](auto &&ow) { return ow->derivedFrom(); }, *m_owner); - return 0; + 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); } -int DomItem::revision() +int DomItem::revision() const { - if (m_owner) - return std::visit([](auto &&ow) { return ow->revision(); }, *m_owner); - else - return -1; + 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); } -QDateTime DomItem::createdAt() +QDateTime DomItem::createdAt() const { - if (m_owner) - return std::visit([](auto &&ow) { return ow->createdAt(); }, *m_owner); - else - return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC); + 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); } -QDateTime DomItem::frozenAt() +QDateTime DomItem::frozenAt() const { - if (m_owner) - return std::visit([](auto &&ow) { return ow->frozenAt(); }, *m_owner); - else - return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC); + 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); } -QDateTime DomItem::lastDataUpdateAt() +QDateTime DomItem::lastDataUpdateAt() const { - if (m_owner) - return std::visit([](auto &&ow) { return ow->lastDataUpdateAt(); }, *m_owner); - else - return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC); + 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); } -void DomItem::addError(ErrorMessage msg) +void DomItem::addError(ErrorMessage &&msg) const { - if (m_owner) { - DomItem myOwner = owner(); - std::visit( - [this, &myOwner, &msg](auto &&ow) { ow->addError(myOwner, msg.withItem(*this)); }, - *m_owner); - } else - defaultErrorHandler(msg.withItem(*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); } -ErrorHandler DomItem::errorHandler() +ErrorHandler DomItem::errorHandler() const { - DomItem self = *this; - return [self](ErrorMessage m) mutable { self.addError(m); }; + // We need a copy here. Error handlers may be called when this is gone. + return [self = *this](const ErrorMessage &m) { self.addError(ErrorMessage(m)); }; } -void DomItem::clearErrors(ErrorGroups groups, bool iterate) +void DomItem::clearErrors(const ErrorGroups &groups, bool iterate) const { - if (m_owner) { - std::visit([&groups](auto &&ow) { ow->clearErrors(groups); }, *m_owner); - if (iterate) - iterateSubOwners([groups](DomItem i){ - i.clearErrors(groups, true); - return true; - }); + 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; + }); } } -bool DomItem::iterateErrors(function_ref<bool(DomItem, ErrorMessage)> visitor, bool iterate, - Path inPath) +bool DomItem::iterateErrors( + function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate, + Path inPath) const { - if (m_owner) { - DomItem ow = owner(); - if (!std::visit([&ow, visitor, - inPath](auto &&el) { return el->iterateErrors(ow, visitor, inPath); }, - *m_owner)) - return false; - if (iterate && !iterateSubOwners([inPath, visitor](DomItem &i) { - return i.iterateErrors(visitor, true, inPath); - })) - return false; + 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; } -bool DomItem::iterateSubOwners(function_ref<bool(DomItem &)> visitor) +bool DomItem::iterateSubOwners(function_ref<bool(const DomItem &)> visitor) const { - if (m_owner) { - DomItem ow = owner(); - return std::visit([&ow, visitor](auto &&o) { return o->iterateSubOwners(ow, visitor); }, - *m_owner); - } - return true; + 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); } -bool DomItem::iterateDirectSubpaths(DirectVisitor v) +bool DomItem::iterateDirectSubpaths(DirectVisitor v) const { - return visitMutableEl( - [this, v](auto &&el) mutable { return el->iterateDirectSubpaths(*this, v); }); + return visitEl( + [this, v](auto &&el) { return el->iterateDirectSubpaths(*this, v); }); } -DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, QList<Path> paths) +DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const { return subListItem( List::fromQList<Path>(pathFromOwner().appendComponent(c), paths, - [](DomItem &list, const PathEls::PathComponent &p, Path &el) { + [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) { return list.subReferenceItem(p, el); })); } -DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, Path referencedObject) +DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const { if (domTypeIsOwningItem(internalKind())) { return DomItem(m_top, m_owner, m_ownerPath, Reference(referencedObject, Path(c))); @@ -2319,126 +2510,45 @@ DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, Path referenc } } -shared_ptr<DomTop> DomItem::topPtr() -{ - if (m_top) - return std::visit([](auto &&el) -> shared_ptr<DomTop> { return el; }, *m_top); - return {}; -} - -shared_ptr<OwningItem> DomItem::owningItemPtr() +shared_ptr<DomTop> DomItem::topPtr() const { - if (m_owner) - return std::visit([](auto &&el) -> shared_ptr<OwningItem> { return el; }, *m_owner); - return {}; + 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); } -const DomBase *DomItem::base() +shared_ptr<OwningItem> DomItem::owningItemPtr() const { - return visitEl([](auto &&el) { return static_cast<const DomBase *>(&(*el)); }); + 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); } -DomBase *DomItem::mutableBase() +/*! + \internal + Returns a pointer to the virtual base pointer to a DomBase. +*/ +const DomBase *DomItem::base() const { - return visitMutableEl([](auto &&el) { return static_cast<DomBase *>(&(*el)); }); + return visitEl([](auto &&el) -> const DomBase * { return el->domBase(); }); } -DomItem::DomItem(std::shared_ptr<DomEnvironment> envPtr): +DomItem::DomItem(const std::shared_ptr<DomEnvironment> &envPtr): DomItem(envPtr, envPtr, Path(), envPtr.get()) { } -DomItem::DomItem(std::shared_ptr<DomUniverse> universePtr): +DomItem::DomItem(const std::shared_ptr<DomUniverse> &universePtr): DomItem(universePtr, universePtr, Path(), universePtr.get()) { } -void DomItem::loadFile(QString canonicalFilePath, QString logicalPath, QString code, - QDateTime codeDate, DomTop::Callback callback, LoadOptions loadOptions, - std::optional<DomType> fileType) -{ - DomItem topEl = top(); - if (topEl.internalKind() == DomType::DomEnvironment - || topEl.internalKind() == DomType::DomUniverse) { - if (auto univ = topEl.ownerAs<DomUniverse>()) - univ->loadFile(*this, canonicalFilePath, logicalPath, code, codeDate, callback, - loadOptions, fileType); - else if (auto env = topEl.ownerAs<DomEnvironment>()) { - if (env->options() & DomEnvironment::Option::NoDependencies) - env->loadFile(topEl, canonicalFilePath, logicalPath, code, codeDate, callback, - DomTop::Callback(), DomTop::Callback(), loadOptions, fileType); - else - env->loadFile(topEl, canonicalFilePath, logicalPath, code, codeDate, - DomTop::Callback(), DomTop::Callback(), callback, loadOptions, - fileType); - } else - Q_ASSERT(false && "expected either DomUniverse or DomEnvironment cast to succeed"); - } else { - addError(myErrors().warning(tr("loadFile called without DomEnvironment or DomUniverse."))); - callback(Paths::qmlFileInfoPath(canonicalFilePath), DomItem::empty, DomItem::empty); - } -} - -void DomItem::loadFile(QString filePath, QString logicalPath, DomTop::Callback callback, - LoadOptions loadOptions, std::optional<DomType> fileType) -{ - DomItem topEl = top(); - if (topEl.internalKind() == DomType::DomEnvironment - || topEl.internalKind() == DomType::DomUniverse) { - if (auto univ = topEl.ownerAs<DomUniverse>()) - univ->loadFile(*this, filePath, logicalPath, callback, loadOptions); - else if (auto env = topEl.ownerAs<DomEnvironment>()) { - if (env->options() & DomEnvironment::Option::NoDependencies) - env->loadFile(topEl, filePath, logicalPath, callback, DomTop::Callback(), - DomTop::Callback(), loadOptions, fileType); - else - env->loadFile(topEl, filePath, logicalPath, DomTop::Callback(), DomTop::Callback(), - callback, loadOptions, fileType); - } else - Q_ASSERT(false && "expected either DomUniverse or DomEnvironment cast to succeed"); - } else { - addError(myErrors().warning(tr("loadFile called without DomEnvironment or DomUniverse."))); - callback(Paths::qmlFileInfoPath(filePath), DomItem::empty, DomItem::empty); - } -} - -void DomItem::loadModuleDependency(QString uri, Version version, - std::function<void(Path, DomItem &, DomItem &)> callback, - ErrorHandler errorHandler) -{ - DomItem topEl = top(); - if (topEl.internalKind() == DomType::DomEnvironment) { - if (auto envPtr = topEl.ownerAs<DomEnvironment>()) { - if (envPtr->options() & DomEnvironment::Option::NoDependencies) - envPtr->loadModuleDependency(topEl, uri, version, callback, nullptr, errorHandler); - else - envPtr->loadModuleDependency(topEl, uri, version, nullptr, callback, errorHandler); - } else - Q_ASSERT(false && "loadDependency expected the DomEnvironment cast to succeed"); - } else { - addError(myErrors().warning(tr("loadModuleDependency called without DomEnvironment."))); - callback(Paths::moduleScopePath(uri, version), DomItem::empty, DomItem::empty); - } -} - -void DomItem::loadBuiltins(std::function<void(Path, DomItem &, DomItem &)> callback, ErrorHandler h) -{ - DomItem env = environment(); - if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) - envPtr->loadBuiltins(env, callback, h); - else - myErrors().error(tr("Cannot load builtins without DomEnvironment")).handle(h); -} - -void DomItem::loadPendingDependencies() -{ - DomItem env = environment(); - if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) - envPtr->loadPendingDependencies(env); - else - myErrors().error(tr("Called loadPendingDependencies without environment")).handle(); -} - /*! \brief Creates a new document with the given code @@ -2447,54 +2557,57 @@ The fileType should normally be QmlFile, but you might want to load a qmltypes f example and interpret it as qmltypes file (not plain Qml), or as JsFile. In those case set the file type accordingly. */ -DomItem DomItem::fromCode(QString code, DomType fileType) +DomItem DomItem::fromCode(const QString &code, DomType fileType) { if (code.isEmpty()) return DomItem(); - DomItem env = + auto env = DomEnvironment::create(QStringList(), QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); DomItem tFile; - env.loadFile( - QString(), QString(), code, QDateTime::currentDateTimeUtc(), + + env->loadFile( + FileToLoad::fromMemory(env, QString(), code), [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, - LoadOption::DefaultLoad, fileType); - env.loadPendingDependencies(); + std::make_optional(fileType)); + env->loadPendingDependencies(); return tFile.fileObject(); } Empty::Empty() {} -Path Empty::pathFromOwner(DomItem &) const +Path Empty::pathFromOwner(const DomItem &) const { return Path(); } -Path Empty::canonicalPath(DomItem &) const +Path Empty::canonicalPath(const DomItem &) const { return Path(); } -bool Empty::iterateDirectSubpaths(DomItem &, DirectVisitor) +bool Empty::iterateDirectSubpaths(const DomItem &, DirectVisitor) const { return true; } -DomItem Empty::containingObject(DomItem &self) const +DomItem Empty::containingObject(const DomItem &self) const { return self; } -void Empty::dump(DomItem &, Sink s, int, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)>) 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) {} @@ -2503,31 +2616,31 @@ quintptr Map::id() const return quintptr(0); } -bool Map::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Map::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { QSet<QString> ksSet = keys(self); QStringList ksList = QStringList(ksSet.begin(), ksSet.end()); std::sort(ksList.begin(), ksList.end()); - for (QString k : ksList) { + for (const QString &k : std::as_const(ksList)) { if (!visitor(PathEls::Key(k), [&self, this, k]() { return key(self, k); })) return false; } return true; } -const QSet<QString> Map::keys(DomItem &self) const +const QSet<QString> Map::keys(const DomItem &self) const { return m_keys(self); } -DomItem Map::key(DomItem &self, QString name) const +DomItem Map::key(const DomItem &self, const QString &name) const { return m_lookup(self, name); } void DomBase::dump( - DomItem &self, Sink sink, int indent, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) const + 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(); @@ -2566,6 +2679,9 @@ void DomBase::dump( case DomKind::Map: sink(u"{"); break; + case DomKind::ScriptElement: + // nothing to print + break; } auto closeParens = qScopeGuard( [dK, sink, indent]{ @@ -2586,6 +2702,9 @@ void DomBase::dump( sinkNewline(sink, indent); sink(u"}"); break; + case DomKind::ScriptElement: + // nothing to print + break; } }); index_type idx = 0; @@ -2639,8 +2758,9 @@ void DomBase::dump( }); } -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) {} @@ -2651,12 +2771,12 @@ quintptr List::id() const } void List::dump( - DomItem &self, Sink sink, int indent, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) const + 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"["); - const_cast<List *>(this)->iterateDirectSubpaths( + iterateDirectSubpaths( self, [&self, indent, &first, sink, filter](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) { @@ -2674,7 +2794,7 @@ void List::dump( sink(u"]"); } -bool List::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool List::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { if (m_iterator) { return m_iterator(self, [visitor](index_type i, function_ref<DomItem()> itemF) { @@ -2689,22 +2809,22 @@ bool List::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return true; } -index_type List::indexes(DomItem &self) const +index_type List::indexes(const DomItem &self) const { return m_length(self); } -DomItem List::index(DomItem &self, index_type index) const +DomItem List::index(const DomItem &self, index_type index) const { return m_lookup(self, index); } -void List::writeOut(DomItem &self, OutWriter &ow, bool compact) const +void List::writeOut(const DomItem &self, OutWriter &ow, bool compact) const { - ow.writeRegion(u"leftSquareBrace", u"["); + ow.writeRegion(LeftBracketRegion); int baseIndent = ow.increaseIndent(1); bool first = true; - const_cast<List *>(this)->iterateDirectSubpaths( + iterateDirectSubpaths( self, [&ow, &first, compact](const PathEls::PathComponent &, function_ref<DomItem()> elF) { if (first) @@ -2720,30 +2840,30 @@ void List::writeOut(DomItem &self, OutWriter &ow, bool compact) const if (!compact && !first) ow.newline(); ow.decreaseIndent(1, baseIndent); - ow.writeRegion(u"rightSquareBrace", u"]"); + ow.writeRegion(RightBracketRegion); } -DomElement::DomElement(Path pathFromOwner) : m_pathFromOwner(pathFromOwner) { } +DomElement::DomElement(const Path &pathFromOwner) : m_pathFromOwner(pathFromOwner) { } -Path DomElement::pathFromOwner(DomItem &) const +Path DomElement::pathFromOwner(const DomItem &) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); return m_pathFromOwner; } -Path DomElement::canonicalPath(DomItem &self) const +Path DomElement::canonicalPath(const DomItem &self) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); return self.owner().canonicalPath().path(m_pathFromOwner); } -DomItem DomElement::containingObject(DomItem &self) const +DomItem DomElement::containingObject(const DomItem &self) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); return DomBase::containingObject(self); } -void DomElement::updatePathFromOwner(Path newPath) +void DomElement::updatePathFromOwner(const Path &newPath) { //if (!domTypeCanBeInline(kind())) m_pathFromOwner = newPath; @@ -2751,7 +2871,7 @@ void DomElement::updatePathFromOwner(Path newPath) bool Reference::shouldCache() const { - for (Path p : referredObjectPath) { + for (const Path &p : referredObjectPath) { switch (p.headKind()) { case Path::Kind::Current: switch (p.headCurrent()) { @@ -2776,7 +2896,7 @@ bool Reference::shouldCache() const return false; } -Reference::Reference(Path referredObject, Path pathFromOwner, const SourceLocation &) +Reference::Reference(const Path &referredObject, const Path &pathFromOwner, const SourceLocation &) : DomElement(pathFromOwner), referredObjectPath(referredObject) { } @@ -2786,7 +2906,7 @@ quintptr Reference::id() const return quintptr(0); } -bool Reference::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool Reference::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueLazyField(visitor, Fields::referredObjectPath, [this]() { @@ -2797,7 +2917,7 @@ bool Reference::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -DomItem Reference::field(DomItem &self, QStringView name) const +DomItem Reference::field(const DomItem &self, QStringView name) const { if (Fields::referredObjectPath == name) return self.subDataItemField(Fields::referredObjectPath, referredObjectPath.toString()); @@ -2806,22 +2926,22 @@ DomItem Reference::field(DomItem &self, QStringView name) const return DomItem(); } -QList<QString> Reference::fields(DomItem &) const +QList<QString> Reference::fields(const DomItem &) const { return QList<QString>({QString::fromUtf16(Fields::referredObjectPath), QString::fromUtf16(Fields::get)}); } -DomItem Reference::index(DomItem &, index_type) const +DomItem Reference::index(const DomItem &, index_type) const { return DomItem(); } -DomItem Reference::key(DomItem &, QString) const +DomItem Reference::key(const DomItem &, const QString &) const { return DomItem(); } -DomItem Reference::get(DomItem &self, ErrorHandler h, QList<Path> *visitedRefs) const +DomItem Reference::get(const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const { DomItem res; if (referredObjectPath) { @@ -2857,7 +2977,7 @@ DomItem Reference::get(DomItem &self, ErrorHandler h, QList<Path> *visitedRefs) QList<Path> visitedRefsLocal; self.resolve( referredObjectPath, - [&res](Path, DomItem &el) { + [&res](Path, const DomItem &el) { res = el; return false; }, @@ -2870,7 +2990,8 @@ DomItem Reference::get(DomItem &self, ErrorHandler h, QList<Path> *visitedRefs) return res; } -QList<DomItem> Reference::getAll(DomItem &self, ErrorHandler h, QList<Path> *visitedRefs) const +QList<DomItem> Reference::getAll( + const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const { QList<DomItem> res; if (referredObjectPath) { @@ -2893,7 +3014,7 @@ QList<DomItem> Reference::getAll(DomItem &self, ErrorHandler h, QList<Path> *vis } if (!cachedPaths.isEmpty()) { bool outdated = false; - for (Path p : cachedPaths) { + for (const Path &p : cachedPaths) { DomItem newEl = env.path(p); if (!newEl) { outdated = true; @@ -2912,22 +3033,23 @@ QList<DomItem> Reference::getAll(DomItem &self, ErrorHandler h, QList<Path> *vis } self.resolve( referredObjectPath, - [&res](Path, DomItem &el) { + [&res](Path, const DomItem &el) { res.append(el); return true; }, h, ResolveOption::None, referredObjectPath, visitedRefs); if (env) { QList<Path> canonicalPaths; - for (DomItem i : res) { + 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, canonicalPaths }); + RefCacheEntry::addForPath( + env, selfPath, + RefCacheEntry { RefCacheEntry::Cached::All, std::move(canonicalPaths) }); } } return res; @@ -2958,7 +3080,7 @@ OwningItem::OwningItem(int derivedFrom) m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) {} -OwningItem::OwningItem(int derivedFrom, QDateTime lastDataUpdateAt) +OwningItem::OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt) : m_derivedFrom(derivedFrom), m_revision(nextRevision()), m_createdAt(QDateTime::currentDateTimeUtc()), @@ -2992,14 +3114,14 @@ int OwningItem::nextRevision() return ++nextRev; } -bool OwningItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool OwningItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvItemField(visitor, Fields::errors, [&self, this]() { QMultiMap<Path, ErrorMessage> myErrors = localErrors(); return self.subMapItem(Map( self.pathFromOwner().field(Fields::errors), - [myErrors](DomItem &map, QString key) { + [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(), @@ -3007,7 +3129,7 @@ bool OwningItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) else return DomItem(); }, - [myErrors](DomItem &) { + [myErrors](const DomItem &) { QSet<QString> res; auto it = myErrors.keyBegin(); auto end = myErrors.keyEnd(); @@ -3020,7 +3142,7 @@ bool OwningItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -DomItem OwningItem::containingObject(DomItem &self) const +DomItem OwningItem::containingObject(const DomItem &self) const { Source s = self.canonicalPath().split(); if (s.pathFromSource) { @@ -3078,12 +3200,12 @@ void OwningItem::refreshedDataAt(QDateTime tNew) m_lastDataUpdateAt = tNew; } -void OwningItem::addError(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()); quint32 &c = m_errorsCounts[msg]; @@ -3092,7 +3214,7 @@ void OwningItem::addErrorLocal(ErrorMessage msg) 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(); @@ -3104,8 +3226,9 @@ void OwningItem::clearErrors(ErrorGroups groups) } } -bool OwningItem::iterateErrors(DomItem &self, function_ref<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; { @@ -3121,7 +3244,7 @@ bool OwningItem::iterateErrors(DomItem &self, function_ref<bool(DomItem, ErrorMe return true; } -bool OwningItem::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor) +bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor) { return self.iterateDirectSubpaths( [&self, visitor](const PathEls::PathComponent &, function_ref<DomItem()> iF) { @@ -3135,13 +3258,11 @@ bool OwningItem::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owne }); } -bool operator==(const DomItem &o1c, const DomItem &o2c) +bool operator==(const DomItem &o1, const DomItem &o2) { - DomItem &o1 = *const_cast<DomItem *>(&o1c); - DomItem &o2 = *const_cast<DomItem *>(&o2c); if (o1.m_kind != o2.m_kind) return false; - return o1.visitMutableEl([&o1, &o2](auto &&el1) { + 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(); @@ -3162,10 +3283,10 @@ bool operator==(const DomItem &o1c, const DomItem &o2c) ErrorHandler MutableDomItem::errorHandler() { MutableDomItem self; - return [&self](ErrorMessage m) { self.addError(m); }; + return [&self](const ErrorMessage &m) { self.addError(ErrorMessage(m)); }; } -MutableDomItem MutableDomItem::addPrototypePath(Path prototypePath) +MutableDomItem MutableDomItem::addPrototypePath(const Path &prototypePath) { if (QmlObject *el = mutableAs<QmlObject>()) { return path(el->addPrototypePath(prototypePath)); @@ -3175,7 +3296,7 @@ MutableDomItem MutableDomItem::addPrototypePath(Path prototypePath) } } -MutableDomItem MutableDomItem::setNextScopePath(Path nextScopePath) +MutableDomItem MutableDomItem::setNextScopePath(const Path &nextScopePath) { if (QmlObject *el = mutableAs<QmlObject>()) { el->setNextScopePath(nextScopePath); @@ -3217,7 +3338,7 @@ MutableDomItem MutableDomItem::setMethods(QMultiMap<QString, MethodInfo> functio return {}; } -MutableDomItem MutableDomItem::setChildren(QList<QmlObject> children) +MutableDomItem MutableDomItem::setChildren(const QList<QmlObject> &children) { if (QmlObject *el = mutableAs<QmlObject>()) { el->setChildren(children); @@ -3227,7 +3348,7 @@ MutableDomItem MutableDomItem::setChildren(QList<QmlObject> children) return {}; } -MutableDomItem MutableDomItem::setAnnotations(QList<QmlObject> annotations) +MutableDomItem MutableDomItem::setAnnotations(const QList<QmlObject> &annotations) { if (QmlObject *el = mutableAs<QmlObject>()) el->setAnnotations(annotations); @@ -3248,7 +3369,7 @@ MutableDomItem MutableDomItem::setAnnotations(QList<QmlObject> annotations) } return field(Fields::annotations); } -MutableDomItem MutableDomItem::setScript(std::shared_ptr<ScriptExpression> exp) +MutableDomItem MutableDomItem::setScript(const std::shared_ptr<ScriptExpression> &exp) { switch (internalKind()) { case DomType::Binding: @@ -3265,8 +3386,14 @@ MutableDomItem MutableDomItem::setScript(std::shared_ptr<ScriptExpression> exp) break; case DomType::MethodParameter: if (MethodParameter *p = mutableAs<MethodParameter>()) { - p->defaultValue = exp; - return field(Fields::body); + 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: @@ -3280,7 +3407,7 @@ MutableDomItem MutableDomItem::setScript(std::shared_ptr<ScriptExpression> exp) return MutableDomItem(); } -MutableDomItem MutableDomItem::setCode(QString code) +MutableDomItem MutableDomItem::setCode(const QString &code) { DomItem it = item(); switch (it.internalKind()) { @@ -3323,7 +3450,8 @@ MutableDomItem MutableDomItem::setCode(QString code) return MutableDomItem(); } -MutableDomItem MutableDomItem::addPropertyDef(PropertyDefinition propertyDef, AddOption option) +MutableDomItem MutableDomItem::addPropertyDef( + const PropertyDefinition &propertyDef, AddOption option) { if (QmlObject *el = mutableAs<QmlObject>()) return el->addPropertyDef(*this, propertyDef, option); @@ -3341,7 +3469,7 @@ MutableDomItem MutableDomItem::addBinding(Binding binding, AddOption option) return MutableDomItem(); } -MutableDomItem MutableDomItem::addMethod(MethodInfo functionDef, AddOption option) +MutableDomItem MutableDomItem::addMethod(const MethodInfo &functionDef, AddOption option) { if (QmlObject *el = mutableAs<QmlObject>()) return el->addMethod(*this, functionDef, option); @@ -3394,17 +3522,17 @@ MutableDomItem MutableDomItem::addAnnotation(QmlObject annotation) return MutableDomItem(owner().item(), res); } -MutableDomItem MutableDomItem::addPreComment(const Comment &comment, QString regionName) +MutableDomItem MutableDomItem::addPreComment(const Comment &comment, FileLocationRegion region) { index_type idx; MutableDomItem rC = field(Fields::comments); if (auto rcPtr = rC.mutableAs<RegionComments>()) { - auto &preList = rcPtr->regionComments[regionName].preComments; - idx = preList.size(); - preList.append(comment); + auto commentedElement = rcPtr->regionComments()[region]; + idx = commentedElement.preComments().size(); + commentedElement.addComment(comment); MutableDomItem res = path(Path::Field(Fields::comments) .field(Fields::regionComments) - .key(regionName) + .key(fileLocationRegionName(region)) .field(Fields::preComments) .index(idx)); Q_ASSERT(res); @@ -3413,17 +3541,17 @@ MutableDomItem MutableDomItem::addPreComment(const Comment &comment, QString reg return MutableDomItem(); } -MutableDomItem MutableDomItem::addPostComment(const Comment &comment, QString regionName) +MutableDomItem MutableDomItem::addPostComment(const Comment &comment, FileLocationRegion region) { index_type idx; MutableDomItem rC = field(Fields::comments); if (auto rcPtr = rC.mutableAs<RegionComments>()) { - auto &postList = rcPtr->regionComments[regionName].postComments; - idx = postList.size(); - postList.append(comment); + auto commentedElement = rcPtr->regionComments()[region]; + idx = commentedElement.postComments().size(); + commentedElement.addComment(comment); MutableDomItem res = path(Path::Field(Fields::comments) .field(Fields::regionComments) - .key(regionName) + .key(fileLocationRegionName(region)) .field(Fields::postComments) .index(idx)); Q_ASSERT(res); @@ -3434,7 +3562,7 @@ MutableDomItem MutableDomItem::addPostComment(const Comment &comment, QString re QDebug operator<<(QDebug debug, const DomItem &c) { - dumperToQDebug([&c](Sink s) { const_cast<DomItem *>(&c)->dump(s); }, debug); + dumperToQDebug([&c](const Sink &s) { c.dump(s); }, debug); return debug; } @@ -3445,7 +3573,7 @@ QDebug operator<<(QDebug debug, const MutableDomItem &c) << ", " << cc.canonicalPath().toString() << ")"; } -bool ListPBase::iterateDirectSubpaths(DomItem &self, DirectVisitor v) +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) { @@ -3455,9 +3583,9 @@ bool ListPBase::iterateDirectSubpaths(DomItem &self, DirectVisitor v) return true; } -void ListPBase::writeOut(DomItem &self, OutWriter &ow, bool compact) const +void ListPBase::writeOut(const DomItem &self, OutWriter &ow, bool compact) const { - ow.writeRegion(u"leftSquareBrace", u"["); + ow.writeRegion(LeftBracketRegion); int baseIndent = ow.increaseIndent(1); bool first = true; index_type len = index_type(m_pList.size()); @@ -3474,7 +3602,36 @@ void ListPBase::writeOut(DomItem &self, OutWriter &ow, bool compact) const if (!compact && !first) ow.newline(); ow.decreaseIndent(1, baseIndent); - ow.writeRegion(u"rightSquareBrace", u"]"); + 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 diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h index 631a0ff335..c27fa48c46 100644 --- a/src/qmldom/qqmldomitem_p.h +++ b/src/qmldom/qqmldomitem_p.h @@ -17,6 +17,7 @@ #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" @@ -24,6 +25,7 @@ #include "qqmldomfunctionref_p.h" #include "qqmldomfilewriter_p.h" #include "qqmldomlinewriter_p.h" +#include "qqmldomfieldfilter_p.h" #include <QtCore/QMap> #include <QtCore/QMultiMap> @@ -36,6 +38,7 @@ #include <QtCore/QCborValue> #include <QtCore/QTimeZone> #include <QtQml/private/qqmljssourcelocation_p.h> +#include <QtQmlCompiler/private/qqmljsscope_p.h> #include <memory> #include <typeinfo> @@ -53,13 +56,14 @@ namespace Dom { class Path; -Q_DECLARE_LOGGING_CATEGORY(writeOutLog); +QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(writeOutLog, QMLDOM_EXPORT); 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); @@ -72,6 +76,7 @@ constexpr bool domTypeCanBeInline(DomType k) case DomType::ListP: case DomType::ConstantData: case DomType::SimpleObjectWrap: + case DomType::ScriptElementWrap: case DomType::Reference: return true; default: @@ -85,15 +90,13 @@ QMLDOM_EXPORT QString domTypeToString(DomType k); QMLDOM_EXPORT QMap<DomKind, QString> domKindToStringMap(); QMLDOM_EXPORT QString domKindToString(DomKind k); -QMLDOM_EXPORT QCborValue locationToData(SourceLocation loc, QStringView strValue=u""); - -inline bool noFilter(DomItem &, const PathEls::PathComponent &, DomItem &) +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, DomItem &)>; +// using DirectVisitor = function_ref<bool(Path, const DomItem &)>; namespace { template<typename T> @@ -176,8 +179,11 @@ 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()); } @@ -192,42 +198,47 @@ union SubclassStorage { ~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(DomItem &self) const = 0; - virtual Path canonicalPath(DomItem &self) const = 0; + virtual Path pathFromOwner(const DomItem &self) const = 0; + virtual Path canonicalPath(const DomItem &self) const = 0; virtual bool - iterateDirectSubpaths(DomItem &self, - DirectVisitor visitor) = 0; // iterates the *direct* subpaths, returns - // false if a quick end was requested - bool iterateDirectSubpathsConst(DomItem &self, DirectVisitor) + 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( - DomItem &self) const; // the DomItem corresponding to the canonicalSource source - virtual void - dump(DomItem &, Sink sink, int indent, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) const; + 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; QString typeName() const; - virtual QList<QString> fields(DomItem &self) const; - virtual DomItem field(DomItem &self, QStringView name) const; + virtual QList<QString> fields(const DomItem &self) const; + virtual DomItem field(const DomItem &self, QStringView name) const; - virtual index_type indexes(DomItem &self) const; - virtual DomItem index(DomItem &self, index_type index) const; + virtual index_type indexes(const DomItem &self) const; + virtual DomItem index(const DomItem &self, index_type index) const; - virtual QSet<QString> const keys(DomItem &self) const; - virtual DomItem key(DomItem &self, QString name) const; + virtual QSet<QString> const keys(const DomItem &self) const; + virtual DomItem key(const DomItem &self, const QString &name) const; - virtual QString canonicalFilePath(DomItem &self) const; + virtual QString canonicalFilePath(const DomItem &self) const; - virtual void writeOut(DomItem &self, OutWriter &lw) const; + virtual void writeOut(const DomItem &self, OutWriter &lw) const; virtual QCborValue value() const { return QCborValue(); @@ -264,12 +275,12 @@ public: Empty(); quintptr id() const override { return ~quintptr(0); } - Path pathFromOwner(DomItem &self) const override; - Path canonicalPath(DomItem &self) const override; - DomItem containingObject(DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - void dump(DomItem &, Sink s, int indent, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) + Path pathFromOwner(const DomItem &self) const override; + Path canonicalPath(const DomItem &self) const override; + DomItem containingObject(const DomItem &self) 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; }; @@ -277,13 +288,14 @@ class QMLDOM_EXPORT DomElement: public DomBase { protected: DomElement& operator=(const DomElement&) = default; public: - DomElement(Path pathFromOwner = Path()); + DomElement(const Path &pathFromOwner = Path()); DomElement(const DomElement &o) = default; - Path pathFromOwner(DomItem &self) const override; + Path pathFromOwner(const DomItem &self) const override; Path pathFromOwner() const { return m_pathFromOwner; } - Path canonicalPath(DomItem &self) const override; - DomItem containingObject(DomItem &self) const override; - virtual void updatePathFromOwner(Path newPath); + Path canonicalPath(const DomItem &self) const override; + DomItem containingObject(const DomItem &self) const override; + virtual void updatePathFromOwner(const Path &newPath); + private: Path m_pathFromOwner; }; @@ -299,22 +311,35 @@ public: Map &operator*() { return *this; } const Map &operator*() const { return *this; } - using LookupFunction = std::function<DomItem(DomItem &, QString)>; - using Keys = std::function<QSet<QString>(DomItem &)>; - Map(Path pathFromOwner, LookupFunction lookup, Keys keys, QString targetType); + 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, DirectVisitor) override; - QSet<QString> const keys(DomItem &self) const override; - DomItem key(DomItem &self, QString name) const override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + QSet<QString> const keys(const DomItem &self) const override; + DomItem key(const DomItem &self, const QString &name) const override; template<typename T> - static Map fromMultiMapRef(Path pathFromOwner, QMultiMap<QString, T> &mmap); + 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(Path pathFromOwner, QMap<QString, T> &mmap, - std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper); + 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); private: + template<typename MapT> + static QSet<QString> fileRegionKeysFromMap(const MapT &map); LookupFunction m_lookup; Keys m_keys; QString m_targetType; @@ -331,32 +356,33 @@ public: List &operator*() { return *this; } const List &operator*() const { return *this; } - using LookupFunction = std::function<DomItem(DomItem &, index_type)>; - using Length = std::function<index_type(DomItem &)>; + using LookupFunction = std::function<DomItem(const DomItem &, index_type)>; + using Length = std::function<index_type(const DomItem &)>; using IteratorFunction = - std::function<bool(DomItem &, function_ref<bool(index_type, function_ref<DomItem()>)>)>; + std::function<bool(const DomItem &, function_ref<bool(index_type, function_ref<DomItem()>)>)>; - List(Path pathFromOwner, LookupFunction lookup, Length length, IteratorFunction iterator, QString elType); + List(const Path &pathFromOwner, const LookupFunction &lookup, const Length &length, + const IteratorFunction &iterator, const QString &elType); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; void - dump(DomItem &, Sink s, int indent, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)>) const override; - index_type indexes(DomItem &self) const override; - DomItem index(DomItem &self, index_type index) const override; + 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(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + 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(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + 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(DomItem &self, OutWriter &ow, bool compact) const; - void writeOut(DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); } + 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; @@ -371,20 +397,20 @@ public: constexpr static DomType kindValue = DomType::ListP; DomType kind() const override { return kindValue; } - ListPBase(Path pathFromOwner, const QList<void *> &pList, QString elType) + ListPBase(const Path &pathFromOwner, const QList<const void *> &pList, const QString &elType) : DomElement(pathFromOwner), m_pList(pList), m_elType(elType) { } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor v) override; + 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(DomItem &) const override { return index_type(m_pList.size()); } - void writeOut(DomItem &self, OutWriter &ow, bool compact) const; - void writeOut(DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); } + 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<void *> m_pList; + QList<const void *> m_pList; QString m_elType; }; @@ -394,7 +420,7 @@ class ListPT final : public ListPBase public: constexpr static DomType kindValue = DomType::ListP; - ListPT(Path pathFromOwner, QList<T *> pList, QString elType = QString(), + 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)) @@ -405,7 +431,7 @@ public: "ListPT does not have the same size as ListPBase"); m_pList.reserve(pList.size()); if (options == ListOptions::Normal) { - for (void *p : pList) + for (const void *p : pList) m_pList.append(p); } else if (options == ListOptions::Reverse) { for (qsizetype i = pList.size(); i-- != 0;) @@ -417,9 +443,9 @@ public: } void copyTo(ListPBase *t) const override { new (t) ListPT(*this); } void moveTo(ListPBase *t) const override { new (t) ListPT(std::move(*this)); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor v) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const override; - DomItem index(DomItem &self, index_type index) const override; + DomItem index(const DomItem &self, index_type index) const override; }; class QMLDOM_EXPORT ListP @@ -427,7 +453,7 @@ class QMLDOM_EXPORT ListP public: constexpr static DomType kindValue = DomType::ListP; template<typename T> - ListP(Path pathFromOwner, QList<T *> pList, QString elType = QString(), + ListP(const Path &pathFromOwner, const QList<T *> &pList, const QString &elType = QString(), ListOptions options = ListOptions::Normal) : list(ListPT<T>(pathFromOwner, pList, elType, options)) { @@ -459,8 +485,9 @@ public: ConstantData &operator*() { return *this; } const ConstantData &operator*() const { return *this; } - ConstantData(Path pathFromOwner, QCborValue value, Options options = Options::MapIsMap); - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + 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; } @@ -484,27 +511,17 @@ public: { if (m_options & SimpleWrapOption::ValueType) { if (m_value.metaType() == QMetaType::fromType<T>()) - return reinterpret_cast<const T *>(m_value.constData()); - return nullptr; - } else { - return m_value.value<T *>(); - } - } - template <typename T> - T *mutableAs() - { - if (m_options & SimpleWrapOption::ValueType) { - if (m_value.metaType() == QMetaType::fromType<T>()) - return reinterpret_cast<T *>(m_value.data()); + return static_cast<const T *>(m_value.constData()); return nullptr; } else { - return m_value.value<T *>(); + return m_value.value<const T *>(); } } + SimpleObjectWrapBase() = delete; virtual void copyTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); } virtual void moveTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); } - bool iterateDirectSubpaths(DomItem &, DirectVisitor) override + bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override { Q_ASSERT(false); return true; @@ -512,7 +529,7 @@ public: protected: friend class TestDomItem; - SimpleObjectWrapBase(Path pathFromOwner, QVariant value, quintptr idValue, + SimpleObjectWrapBase(const Path &pathFromOwner, const QVariant &value, quintptr idValue, DomType kind = kindValue, SimpleWrapOptions options = SimpleWrapOption::None) : DomElement(pathFromOwner), @@ -537,21 +554,21 @@ class SimpleObjectWrapT final : public SimpleObjectWrapBase public: constexpr static DomType kindValue = DomType::SimpleObjectWrap; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override { - return mutableAsT()->iterateDirectSubpaths(self, visitor); + return asT()->iterateDirectSubpaths(self, visitor); } - void writeOut(DomItem &self, OutWriter &lw) const override; + void writeOut(const DomItem &self, OutWriter &lw) const override; - T const *asT() const + const T *asT() const { if constexpr (domTypeIsValueWrap(T::kindValue)) { if (m_value.metaType() == QMetaType::fromType<T>()) - return reinterpret_cast<const T *>(m_value.constData()); + return static_cast<const T *>(m_value.constData()); return nullptr; } else if constexpr (domTypeIsObjWrap(T::kindValue)) { - return m_value.value<T *>(); + 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"); @@ -559,20 +576,6 @@ public: } } - T *mutableAsT() - { - if (domTypeIsValueWrap(T::kindValue)) { - if (m_value.metaType() == QMetaType::fromType<T>()) - return reinterpret_cast<T *>(m_value.data()); - return nullptr; - } else if constexpr (domTypeIsObjWrap(T::kindValue)) { - return m_value.value<T *>(); - } else { - Q_ASSERT_X(false, "SimpleObjectWrap", "wrapping of unexpected type"); - return nullptr; - } - } - void copyTo(SimpleObjectWrapBase *target) const override { static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT), @@ -591,7 +594,8 @@ public: new (target) SimpleObjectWrapT(std::move(*this)); } - SimpleObjectWrapT(Path pathFromOwner, QVariant v, quintptr idValue, SimpleWrapOptions o) + 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)); @@ -609,7 +613,7 @@ public: const SimpleObjectWrapBase &operator*() const { return *wrap.data(); } template<typename T> - static SimpleObjectWrap fromObjectRef(Path pathFromOwner, T &value) + static SimpleObjectWrap fromObjectRef(const Path &pathFromOwner, T &value) { return SimpleObjectWrap(pathFromOwner, value); } @@ -617,7 +621,7 @@ public: private: template<typename T> - SimpleObjectWrap(Path pathFromOwner, T &value) + SimpleObjectWrap(const Path &pathFromOwner, T &value) { using BaseT = std::decay_t<T>; if constexpr (domTypeIsObjWrap(BaseT::kindValue)) { @@ -651,57 +655,248 @@ public: const Reference &operator*() const { return *this; } bool shouldCache() const; - Reference(Path referredObject = Path(), Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation()); + Reference(const Path &referredObject = Path(), const Path &pathFromOwner = Path(), + const SourceLocation &loc = SourceLocation()); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - DomItem field(DomItem &self, QStringView name) const override; - QList<QString> fields(DomItem &self) const override; - index_type indexes(DomItem &) const override { return 0; } - DomItem index(DomItem &, index_type) const override; - QSet<QString> const keys(DomItem &) const override { return {}; } - DomItem key(DomItem &, QString) const override; - - DomItem get(DomItem &self, ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr) const; - QList<DomItem> getAll(DomItem &self, ErrorHandler h = nullptr, + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + DomItem field(const DomItem &self, QStringView name) const override; + 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 &, const QString &) const override; + + 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; }; -using ElementT = std::variant< - Empty, Map, List, ListP, ConstantData, SimpleObjectWrap, Reference, GlobalComponent *, - JsResource *, QmlComponent *, QmltypesComponent *, EnumDecl *, MockObject *, ModuleScope *, - AstComments *, AttachedInfo *, DomEnvironment *, DomUniverse *, ExternalItemInfoBase *, - ExternalItemPairBase *, GlobalScope *, JsFile *, QmlDirectory *, QmlFile *, QmldirFile *, - QmlObject *, QmltypesFile *, LoadInfo *, MockOwner *, ModuleIndex *, ScriptExpression *>; +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; -using TopT = std::variant<std::shared_ptr<DomEnvironment>, std::shared_ptr<DomUniverse>>; + 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(); } -using OwnerT = - std::variant<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>>; + ScriptElementVariant element() const { return m_element; } -inline bool emptyChildrenVisitor(Path, DomItem &, bool) +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, DomItem &, DomItem &)>; + using Callback = function<void(const Path &, const DomItem &, const DomItem &)>; using InternalKind = DomType; - using Visitor = function_ref<bool(Path, DomItem &)>; - using ChildrenVisitor = function_ref<bool(Path, 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(); @@ -711,12 +906,7 @@ public: enum class CopyOption { EnvConnected, EnvDisconnected }; template<typename F> - auto visitMutableEl(F f) - { - return std::visit(f, this->m_element); - } - template<typename F> - auto visitEl(F f) + auto visitEl(F f) const { return std::visit(f, this->m_element); } @@ -734,48 +924,55 @@ public: return kind2domKind(m_kind); } - Path canonicalPath(); + Path canonicalPath() const; - DomItem filterUp(function_ref<bool(DomType k, DomItem &)> filter, FilterUpOptions options); - DomItem containingObject(); - DomItem container(); - DomItem owner(); - DomItem top(); - DomItem environment(); - DomItem universe(); + DomItem filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const; + DomItem containingObject() const; + DomItem container() 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); - DomItem fileObject(GoTo option = GoTo::Strict); - DomItem rootQmlObject(GoTo option = GoTo::Strict); - DomItem globalScope(); - DomItem component(GoTo option = GoTo::Strict); - DomItem scope(FilterUpOptions options = FilterUpOptions::ReturnOuter); + 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 - DomItem get(ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr); - QList<DomItem> getAll(ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr); - bool isOwningItem() { return domTypeIsOwningItem(internalKind()); } - bool isExternalItem() { return domTypeIsExternalItem(internalKind()); } - bool isTopItem() { return domTypeIsTopItem(internalKind()); } - bool isContainer() { return domTypeIsContainer(internalKind()); } - bool isScope() { return domTypeIsScope(internalKind()); } - bool isCanonicalChild(DomItem &child); - bool hasAnnotations(); - QString name() { return field(Fields::name).value().toString(); } - DomItem pragmas() { return field(Fields::pragmas); } - DomItem ids() { return field(Fields::ids); } - QString idStr() { return field(Fields::idStr).value().toString(); } - DomItem propertyInfos() { return field(Fields::propertyInfos); } - PropertyInfo propertyInfoWithName(QString name); - QSet<QString> propertyInfoNames(); - DomItem propertyDefs() { return field(Fields::propertyDefs); } - DomItem bindings() { return field(Fields::bindings); } - DomItem methods() { return field(Fields::methods); } - DomItem enumerations() { return field(Fields::enumerations); } - DomItem children() { return field(Fields::children); } - DomItem child(index_type i) { return field(Fields::children).index(i); } - DomItem annotations() + 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); @@ -783,200 +980,214 @@ public: return DomItem(); } - bool resolve(Path path, Visitor visitor, ErrorHandler errorHandler, - ResolveOptions options = ResolveOption::None, Path fullPath = Path(), - QList<Path> *visitedRefs = nullptr); + 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); - DomItem operator[](QStringView component); - DomItem operator[](const QString &component); - DomItem operator[](const char16_t *component) + 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[](index_type i) { return index(i); } - DomItem operator[](int i) { return index(i); } - index_type size() { return indexes() + keys().size(); } - index_type length() { return size(); } - - DomItem path(Path p, ErrorHandler h = &defaultErrorHandler); - DomItem path(QString p, ErrorHandler h = &defaultErrorHandler); - DomItem path(QStringView p, ErrorHandler h = &defaultErrorHandler); - - QList<QString> fields(); - DomItem field(QStringView name); - - index_type indexes(); - DomItem index(index_type); - bool visitIndexes(function_ref<bool(DomItem &)> visitor); - - QSet<QString> keys(); - QStringList sortedKeys(); - DomItem key(QString name); - DomItem key(QStringView name) { return key(name.toString()); } - bool visitKeys(function_ref<bool(QString, DomItem &)> visitor); - - QList<DomItem> values(); - void writeOutPre(OutWriter &lw); - void writeOut(OutWriter &lw); - void writeOutPost(OutWriter &lw); - DomItem writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks); - DomItem writeOut(QString path, int nBackups = 2, - const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr, - WriteOutChecks extraChecks = WriteOutCheck::Default); - - bool visitTree(Path basePath, ChildrenVisitor visitor, + 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(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> fields() const; + DomItem field(QStringView name) const; + + index_type indexes() const; + DomItem index(index_type) const; + 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); - bool visitPrototypeChain(function_ref<bool(DomItem &)> visitor, + ChildrenVisitor closingVisitor = emptyChildrenVisitor, + const FieldFilter &filter = FieldFilter::noFilter()) const; + bool visitPrototypeChain(function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options = VisitPrototypesOption::Normal, - ErrorHandler h = nullptr, QSet<quintptr> *visited = nullptr, - QList<Path> *visitedRefs = nullptr); - bool visitDirectAccessibleScopes(function_ref<bool(DomItem &)> visitor, + 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, - ErrorHandler h = nullptr, QSet<quintptr> *visited = nullptr, - QList<Path> *visitedRefs = nullptr); + const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr) const; bool - visitStaticTypePrototypeChains(function_ref<bool(DomItem &)> visitor, + visitStaticTypePrototypeChains(function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options = VisitPrototypesOption::Normal, - ErrorHandler h = nullptr, QSet<quintptr> *visited = nullptr, - QList<Path> *visitedRefs = nullptr); - bool visitScopeChain(function_ref<bool(DomItem &)> visitor, - LookupOptions = LookupOption::Normal, ErrorHandler h = nullptr, - QSet<quintptr> *visited = nullptr, QList<Path> *visitedRefs = nullptr); - bool visitLocalSymbolsNamed(QString name, function_ref<bool(DomItem &)> visitor); - QSet<QString> localSymbolNames(LocalSymbolsTypes lTypes = LocalSymbolsType::All); - bool visitLookup1(QString symbolName, function_ref<bool(DomItem &)> visitor, - LookupOptions = LookupOption::Normal, ErrorHandler h = nullptr, - QSet<quintptr> *visited = nullptr, QList<Path> *visitedRefs = nullptr); - bool visitLookup(QString symbolName, function_ref<bool(DomItem &)> visitor, - LookupType type = LookupType::Symbol, LookupOptions = LookupOption::Normal, - ErrorHandler errorHandler = nullptr, QSet<quintptr> *visited = nullptr, - QList<Path> *visitedRefs = nullptr); - bool visitSubSymbolsNamed(QString name, function_ref<bool(DomItem &)> visitor); - DomItem proceedToScope(ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr); - QList<DomItem> lookup(QString symbolName, LookupType type = LookupType::Symbol, - LookupOptions = LookupOption::Normal, - ErrorHandler errorHandler = nullptr); - DomItem lookupFirst(QString symbolName, LookupType type = LookupType::Symbol, - LookupOptions = LookupOption::Normal, ErrorHandler errorHandler = nullptr); - - quintptr id(); - Path pathFromOwner(); - QString canonicalFilePath(); - DomItem fileLocationsTree(); - DomItem fileLocations(); - MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected); - bool commitToBase(std::shared_ptr<DomEnvironment> validPtr = nullptr); - DomItem refreshed() { return top().path(canonicalPath()); } - QCborValue value(); - - void dumpPtr(Sink sink); - void dump(Sink, int indent = 0, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = - noFilter); + 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(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(QString path, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, - int nBackups = 2, int indent = 0, FileWriter *fw = nullptr); - QString toString(); - QString toString() const - { - DomItem self = *this; - return self.toString(); - } + 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 - int derivedFrom(); - int revision(); - QDateTime createdAt(); - QDateTime frozenAt(); - QDateTime lastDataUpdateAt(); + int derivedFrom() const; + int revision() const; + QDateTime createdAt() const; + QDateTime frozenAt() const; + QDateTime lastDataUpdateAt() const; - void addError(ErrorMessage msg); - ErrorHandler errorHandler(); - void clearErrors(ErrorGroups groups = ErrorGroups({}), bool iterate = true); + void addError(ErrorMessage &&msg) const; + ErrorHandler errorHandler() const; + void clearErrors(const ErrorGroups &groups = ErrorGroups({}), bool iterate = true) const; // return false if a quick exit was requested - bool iterateErrors(function_ref<bool(DomItem source, ErrorMessage msg)> visitor, bool iterate, - Path inPath = Path()); + bool iterateErrors( + function_ref<bool (const DomItem &, const ErrorMessage &)> visitor, bool iterate, + Path inPath = Path()) const; - bool iterateSubOwners(function_ref<bool(DomItem &owner)> visitor); - bool iterateDirectSubpaths(DirectVisitor v); + bool iterateSubOwners(function_ref<bool(const DomItem &owner)> visitor) const; + bool iterateDirectSubpaths(DirectVisitor v) const; template<typename T> - DomItem subDataItem(const PathEls::PathComponent &c, T value, - ConstantData::Options options = ConstantData::Options::MapIsMap); + DomItem subDataItem(const PathEls::PathComponent &c, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const; template<typename T> - DomItem subDataItemField(QStringView f, T value, - ConstantData::Options options = ConstantData::Options::MapIsMap) + 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, T value, - ConstantData::Options options = ConstantData::Options::MapIsMap); + 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, T value, - ConstantData::Options options = ConstantData::Options::MapIsMap); + 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, T value, - ConstantData::Options options = ConstantData::Options::MapIsMap) + bool dvValueField(DirectVisitor visitor, QStringView f, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const { - return this->dvValue<T>(visitor, PathEls::Field(f), value, options); + 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); + ConstantData::Options options = ConstantData::Options::MapIsMap) const; template<typename F> bool dvValueLazyField(DirectVisitor visitor, QStringView f, F valueF, - ConstantData::Options options = ConstantData::Options::MapIsMap) + ConstantData::Options options = ConstantData::Options::MapIsMap) const { - return this->dvValueLazy(visitor, PathEls::Field(f), valueF, options); + return this->dvValueLazy(std::move(visitor), PathEls::Field(f), valueF, options); } - DomItem subLocationItem(const PathEls::PathComponent &c, SourceLocation loc, - QStringView code = QStringView()) + DomItem subLocationItem(const PathEls::PathComponent &c, SourceLocation loc) const { - return this->subDataItem(c, locationToData(loc, code)); + return this->subDataItem(c, sourceLocationToQCborValue(loc)); } // bool dvSubReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path // referencedObject); - DomItem subReferencesItem(const PathEls::PathComponent &c, QList<Path> paths); - DomItem subReferenceItem(const PathEls::PathComponent &c, Path referencedObject); - bool dvReference(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(visitor, c, [c, this, referencedObject]() { + return dvItem(std::move(visitor), c, [c, this, referencedObject]() { return this->subReferenceItem(c, referencedObject); }); } - bool dvReferences(DirectVisitor visitor, const PathEls::PathComponent &c, QList<Path> paths) + bool dvReferences( + DirectVisitor visitor, const PathEls::PathComponent &c, const QList<Path> &paths) const { - return dvItem(visitor, c, [c, this, paths]() { return this->subReferencesItem(c, paths); }); + return dvItem(std::move(visitor), c, [c, this, paths]() { + return this->subReferencesItem(c, paths); + }); } - bool dvReferenceField(DirectVisitor visitor, QStringView f, Path referencedObject) + bool dvReferenceField(DirectVisitor visitor, QStringView f, const Path &referencedObject) const { - return dvReference(visitor, PathEls::Field(f), referencedObject); + return dvReference(std::move(visitor), PathEls::Field(f), referencedObject); } - bool dvReferencesField(DirectVisitor visitor, QStringView f, QList<Path> paths) + bool dvReferencesField(DirectVisitor visitor, QStringView f, const QList<Path> &paths) const { - return dvReferences(visitor, PathEls::Field(f), paths); + return dvReferences(std::move(visitor), PathEls::Field(f), paths); } - bool dvItem(DirectVisitor visitor, const PathEls::PathComponent &c, function_ref<DomItem()> it) + 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) + bool dvItemField(DirectVisitor visitor, QStringView f, function_ref<DomItem()> it) const { - return dvItem(visitor, PathEls::Field(f), it); + return dvItem(std::move(visitor), PathEls::Field(f), it); } - DomItem subListItem(const List &list); - DomItem subMapItem(const Map &map); - DomItem subObjectWrapItem(SimpleObjectWrap obj) + 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) + 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()); @@ -984,46 +1195,35 @@ public: return DomItem(m_top, o, Path(), o.get()); } template<typename T> - DomItem wrap(const PathEls::PathComponent &c, T &obj); + DomItem wrap(const PathEls::PathComponent &c, const T &obj) const; template<typename T> - DomItem wrapField(QStringView f, T &obj) + 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); + bool dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) const; template<typename T> - bool dvWrapField(DirectVisitor visitor, QStringView f, T &obj) + bool dvWrapField(DirectVisitor visitor, QStringView f, T &obj) const { - return dvWrap<T>(visitor, PathEls::Field(f), obj); + return dvWrap<T>(std::move(visitor), PathEls::Field(f), obj); } DomItem() = default; - DomItem(std::shared_ptr<DomEnvironment>); - DomItem(std::shared_ptr<DomUniverse>); - - static DomItem fromCode(QString code, DomType fileType = DomType::QmlFile); - void loadFile(QString filePath, QString logicalPath, - std::function<void(Path, DomItem &, DomItem &)> callback, LoadOptions loadOptions, - std::optional<DomType> fileType = std::optional<DomType>()); - void loadFile(QString canonicalFilePath, QString logicalPath, QString code, QDateTime codeDate, - std::function<void(Path, DomItem &, DomItem &)> callback, LoadOptions loadOptions, - std::optional<DomType> fileType = std::optional<DomType>()); - void loadModuleDependency(QString uri, Version v, - std::function<void(Path, DomItem &, DomItem &)> callback = nullptr, - ErrorHandler = nullptr); - void loadBuiltins(std::function<void(Path, DomItem &, DomItem &)> callback = nullptr, - ErrorHandler = nullptr); - void loadPendingDependencies(); + 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? --- - std::shared_ptr<DomTop> topPtr(); - std::shared_ptr<OwningItem> owningItemPtr(); + std::shared_ptr<DomTop> topPtr() const; + 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_v<DomBase, T>, bool>::type = true> - T const *as() + T const *as() const { if (m_kind == T::kindValue) { if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue)) @@ -1035,7 +1235,7 @@ public: } template<typename T, typename std::enable_if<!std::is_base_of_v<DomBase, T>, bool>::type = true> - T const *as() + T const *as() const { if (m_kind == T::kindValue) { Q_ASSERT(domTypeIsObjWrap(m_kind) || domTypeIsValueWrap(m_kind)); @@ -1045,67 +1245,54 @@ public: } template<typename T> - std::shared_ptr<T> ownerAs(); + std::shared_ptr<T> ownerAs() const; template<typename Owner, typename T> - DomItem copy(Owner owner, Path ownerPath, T base) + DomItem copy(const Owner &owner, const Path &ownerPath, const T &base) const { - Q_ASSERT(m_top); + 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 Owner> - DomItem copy(Owner owner, Path ownerPath) + DomItem copy(const Owner &owner, const Path &ownerPath) const { - Q_ASSERT(m_top); + Q_ASSERT(!std::holds_alternative<std::monostate>(m_top)); return DomItem(m_top, owner, ownerPath, owner.get()); } template<typename T> - DomItem copy(T base) + DomItem copy(const T &base) const { - Q_ASSERT(m_top); + 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, "expected either a pointer or an inline item"); - if constexpr (IsSharedPointerToDomObject<BaseT>::value) { + 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 { + 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: - DomBase const *base(); - template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true> - T *mutableAs() { - if (m_kind == T::kindValue) { - if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue)) - return static_cast<SimpleObjectWrapBase *>(mutableBase())->mutableAs<T>(); - else - return static_cast<T *>(mutableBase()); - } - return nullptr; - } + enum class WriteOutCheckResult { Success, Failed }; + WriteOutCheckResult performWriteOutChecks(const DomItem &, const DomItem &, OutWriter &, WriteOutChecks) const; + const DomBase *base() const; - template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true> - T *mutableAs() { - if (m_kind == T::kindValue) { - Q_ASSERT(domTypeIsObjWrap(m_kind) || domTypeIsValueWrap(m_kind)); - return static_cast<SimpleObjectWrapBase *>(mutableBase())->mutableAs<T>(); - } - return nullptr; - } - DomBase *mutableBase(); 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, Path ownerPath, T el) + 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>; @@ -1113,8 +1300,8 @@ private: if (!el || el->kind() == DomType::Empty) { // avoid null ptr, and allow only a // single kind of Empty m_kind = DomType::Empty; - m_top.reset(); - m_owner.reset(); + m_top = std::monostate(); + m_owner = std::monostate(); m_ownerPath = Path(); m_element = Empty(); } else { @@ -1144,8 +1331,8 @@ private: friend class TestDomItem; friend QMLDOM_EXPORT bool operator==(const DomItem &, const DomItem &); DomType m_kind = DomType::Empty; - std::optional<TopT> m_top; - std::optional<OwnerT> m_owner; + TopT m_top; + OwnerT m_owner; Path m_ownerPath; ElementT m_element = Empty(); }; @@ -1158,123 +1345,179 @@ inline bool operator!=(const DomItem &o1, const DomItem &o2) } template<typename T> -Map Map::fromMultiMapRef(Path pathFromOwner, QMultiMap<QString, T> &mmap) +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); + } +} + +template<typename T> +Map Map::fromMultiMapRef(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap) { return Map( pathFromOwner, - [&mmap](DomItem &self, QString key) { - 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<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); - } + [&mmap](const DomItem &self, const QString &key) { + return keyMultiMapHelper(self, key, mmap); }, - [&mmap](DomItem &) { return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); }, + [&mmap](const DomItem &) { return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); }, QLatin1String(typeid(T).name())); } template<typename T> Map Map::fromMapRef( - Path pathFromOwner, QMap<QString, T> &map, - std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper) + 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](DomItem &self, QString key) { - if (!map.contains(key)) + [&map, elWrapper](const DomItem &self, const QString &key) { + const auto it = map.constFind(key); + if (it == map.constEnd()) return DomItem(); - else { - return elWrapper(self, PathEls::Key(key), map[key]); - } + return elWrapper(self, PathEls::Key(key), it.value()); }, - [&map](DomItem &) { return QSet<QString>(map.keyBegin(), map.keyEnd()); }, + [&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::fromFileRegionMap(const Path &pathFromOwner, const QMap<FileLocationRegion, T> &map) +{ + 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::fromFileRegionListMap(const Path &pathFromOwner, + const QMap<FileLocationRegion, QList<T>> &map) +{ + 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(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + 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.size(); if (options == ListOptions::Reverse) { return List( pathFromOwner, - [list, elWrapper](DomItem &self, index_type i) mutable { + [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](DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); + [len](const DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); } else { return List( pathFromOwner, - [list, elWrapper](DomItem &self, index_type i) mutable { + [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](DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); + [len](const DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); } } template<typename T> List List::fromQListRef( - Path pathFromOwner, QList<T> &list, - std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + 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](DomItem &self, index_type i) { + [&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](DomItem &) { return list.size(); }, nullptr, + [&list](const DomItem &) { return list.size(); }, nullptr, QLatin1String(typeid(T).name())); } else { return List( pathFromOwner, - [&list, elWrapper](DomItem &self, index_type i) { + [&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](DomItem &) { return list.size(); }, nullptr, + [&list](const DomItem &) { return list.size(); }, nullptr, QLatin1String(typeid(T).name())); } } class QMLDOM_EXPORT OwningItem: public DomBase { protected: - virtual std::shared_ptr<OwningItem> doCopy(DomItem &self) const = 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(DomItem &self) const override = 0; + Path canonicalPath(const DomItem &self) const override = 0; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - std::shared_ptr<OwningItem> makeCopy(DomItem &self) const { return doCopy(self); } + 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(DomItem &) const override final { return Path(); } - DomItem containingObject(DomItem &self) const override; + Path pathFromOwner(const DomItem &) const override final { return Path(); } + DomItem containingObject(const DomItem &self) const override; int derivedFrom() const; virtual int revision() const; @@ -1287,18 +1530,20 @@ public: virtual bool freeze(); QDateTime frozenAt() const; - virtual void addError(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(DomItem &self, function_ref<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(DomItem &self, function_ref<bool(DomItem &owner)> visitor); + virtual bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor); QBasicMutex *mutex() const { return &m_mutex; } private: @@ -1313,25 +1558,25 @@ private: }; template<typename T> -std::shared_ptr<T> DomItem::ownerAs() +std::shared_ptr<T> DomItem::ownerAs() const { if constexpr (domTypeIsOwningItem(T::kindValue)) { - if (m_owner) { + if (!std::holds_alternative<std::monostate>(m_owner)) { if constexpr (T::kindValue == DomType::AttachedInfo) { - if (std::holds_alternative<std::shared_ptr<AttachedInfo>>(*m_owner)) + if (std::holds_alternative<std::shared_ptr<AttachedInfo>>(m_owner)) return std::static_pointer_cast<T>( - std::get<std::shared_ptr<AttachedInfo>>(*m_owner)); + 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)) + if (std::holds_alternative<std::shared_ptr<ExternalItemInfoBase>>(m_owner)) return std::static_pointer_cast<T>( - std::get<std::shared_ptr<ExternalItemInfoBase>>(*m_owner)); + 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)) + if (std::holds_alternative<std::shared_ptr<ExternalItemPairBase>>(m_owner)) return std::static_pointer_cast<T>( - std::get<std::shared_ptr<ExternalItemPairBase>>(*m_owner)); + 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); + if (std::holds_alternative<std::shared_ptr<T>>(m_owner)) { + return std::get<std::shared_ptr<T>>(m_owner); } } } @@ -1352,26 +1597,26 @@ struct rank<0> }; template<typename T> -auto writeOutWrap(const T &t, DomItem &self, OutWriter &lw, rank<1>) +auto writeOutWrap(const T &t, const DomItem &self, OutWriter &lw, rank<1>) -> decltype(t.writeOut(self, lw)) { t.writeOut(self, lw); } template<typename T> -auto writeOutWrap(const T &, DomItem &, OutWriter &, rank<0>) -> void +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> -auto writeOutWrap(const T &t, DomItem &self, OutWriter &lw) -> void +auto writeOutWrap(const T &t, const DomItem &self, OutWriter &lw) -> void { writeOutWrap(t, self, lw, rank<1>()); } template<typename T> -void SimpleObjectWrapT<T>::writeOut(DomItem &self, OutWriter &lw) const +void SimpleObjectWrapT<T>::writeOut(const DomItem &self, OutWriter &lw) const { writeOutWrap<T>(*asT(), self, lw); } @@ -1390,7 +1635,7 @@ public: QString internalKindStr() { return domTypeToString(internalKind()); } DomKind domKind() { return kind2domKind(internalKind()); } - Path canonicalPath() { return m_owner.canonicalPath().path(m_pathFromOwner); } + Path canonicalPath() const { return m_owner.canonicalPath().path(m_pathFromOwner); } MutableDomItem containingObject() { if (m_pathFromOwner) @@ -1458,28 +1703,27 @@ public: MutableDomItem index(index_type i) { return MutableDomItem(item().index(i)); } QSet<QString> const keys() { return item().keys(); } - MutableDomItem key(QString name) { return MutableDomItem(item().key(name)); } + MutableDomItem key(const QString &name) { return MutableDomItem(item().key(name)); } MutableDomItem key(QStringView name) { return key(name.toString()); } void - dump(Sink s, int indent = 0, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter) + dump(const Sink &s, int indent = 0, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter) { item().dump(s, indent, filter); } FileWriter::Status - dump(QString path, - function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter, + 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); } void writeOut(OutWriter &lw) { return item().writeOut(lw); } - MutableDomItem writeOut(QString path, int nBackups = 2, - const LineWriterOptions &opt = LineWriterOptions(), - FileWriter *fw = nullptr) + bool writeOut(const QString &path, int nBackups = 2, + const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr) { - return MutableDomItem(item().writeOut(path, nBackups, opt, fw)); + return item().writeOut(path, nBackups, opt, fw); } MutableDomItem fileLocations() { return MutableDomItem(item().fileLocations()); } @@ -1487,11 +1731,11 @@ public: { return item().makeCopy(option); } - bool commitToBase(std::shared_ptr<DomEnvironment> validEnvPtr = nullptr) + bool commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr = nullptr) { return item().commitToBase(validEnvPtr); } - QString canonicalFilePath() { return item().canonicalFilePath(); } + QString canonicalFilePath() const { return item().canonicalFilePath(); } MutableDomItem refreshed() { return MutableDomItem(item().refreshed()); } @@ -1518,41 +1762,36 @@ public: QDateTime frozenAt() { return m_owner.frozenAt(); } QDateTime lastDataUpdateAt() { return m_owner.lastDataUpdateAt(); } - void addError(ErrorMessage msg) { item().addError(msg); } + void addError(ErrorMessage &&msg) { item().addError(std::move(msg)); } ErrorHandler errorHandler(); // convenience setters - MutableDomItem addPrototypePath(Path prototypePath); - MutableDomItem setNextScopePath(Path nextScopePath); + 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(QList<QmlObject> children); - MutableDomItem setAnnotations(QList<QmlObject> annotations); - MutableDomItem setScript(std::shared_ptr<ScriptExpression> exp); - MutableDomItem setCode(QString code); - MutableDomItem addPropertyDef(PropertyDefinition propertyDef, + 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(MethodInfo functionDef, 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, QString regionName = QString()); - MutableDomItem addPreComment(const Comment &comment, QStringView regionName) - { - return addPreComment(comment, regionName.toString()); - } - MutableDomItem addPostComment(const Comment &comment, QString regionName = QString()); - MutableDomItem addPostComment(const Comment &comment, QStringView regionName) - { - return addPostComment(comment, regionName.toString()); - } + 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()) {} @@ -1568,18 +1807,32 @@ public: template <typename T> T *mutableAs() { Q_ASSERT(!m_owner || !m_owner.owningItemPtr()->frozen()); - return item().mutableAs<T>(); + + 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() + std::shared_ptr<T> ownerAs() const { return m_owner.ownerAs<T>(); } // it is dangerous to assume it stays valid when updates are preformed... - DomItem item() { return m_owner.path(m_pathFromOwner); } + DomItem item() const { return m_owner.path(m_pathFromOwner); } - friend bool operator==(const MutableDomItem o1, const MutableDomItem &o2) + friend bool operator==(const MutableDomItem &o1, const MutableDomItem &o2) { return o1.m_owner == o2.m_owner && o1.m_pathFromOwner == o2.m_pathFromOwner; } @@ -1596,7 +1849,7 @@ private: QMLDOM_EXPORT QDebug operator<<(QDebug debug, const MutableDomItem &c); template<typename K, typename T> -Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mmap, K key, +Path insertUpdatableElementInMultiMap(const Path &mapPathFromOwner, QMultiMap<K, T> &mmap, K key, const T &value, AddOption option = AddOption::KeepExisting, T **valuePtr = nullptr) { @@ -1633,7 +1886,7 @@ Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mm } template<typename T> -Path appendUpdatableElementInQList(Path listPathFromOwner, QList<T> &list, const T &value, +Path appendUpdatableElementInQList(const Path &listPathFromOwner, QList<T> &list, const T &value, T **vPtr = nullptr) { int idx = list.size(); @@ -1647,7 +1900,7 @@ Path appendUpdatableElementInQList(Path listPathFromOwner, QList<T> &list, const } 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(); @@ -1676,7 +1929,7 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) } 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(); @@ -1788,9 +2041,14 @@ constexpr bool domTypeIsUnattachedOwningItem(DomType k) } } +constexpr bool domTypeIsScriptElement(DomType k) +{ + return DomType::ScriptElementStart <= k && k <= DomType::ScriptElementStop; +} + template<typename T> -DomItem DomItem::subValueItem(const PathEls::PathComponent &c, T value, - ConstantData::Options options) +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 ( @@ -1805,8 +2063,8 @@ DomItem DomItem::subValueItem(const PathEls::PathComponent &c, T 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](DomItem &list, const PathEls::PathComponent &p, - typename T::value_type &v) { return list.subValueItem(p, v, options); })); + [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); @@ -1816,8 +2074,8 @@ DomItem DomItem::subValueItem(const PathEls::PathComponent &c, T value, } template<typename T> -DomItem DomItem::subDataItem(const PathEls::PathComponent &c, T value, - ConstantData::Options options) +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>) { @@ -1833,8 +2091,8 @@ DomItem DomItem::subDataItem(const PathEls::PathComponent &c, T value, } template<typename T> -bool DomItem::dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, T value, - ConstantData::Options options) +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); @@ -1844,7 +2102,7 @@ bool DomItem::dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, T template<typename F> bool DomItem::dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF, - ConstantData::Options options) + ConstantData::Options options) const { auto lazyWrap = [this, &c, &valueF, options]() { return this->subValueItem<decltype(valueF())>(c, valueF(), options); @@ -1853,7 +2111,7 @@ bool DomItem::dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c } template<typename T> -DomItem DomItem::wrap(const PathEls::PathComponent &c, T &obj) +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>) { @@ -1904,8 +2162,8 @@ DomItem DomItem::wrap(const PathEls::PathComponent &c, T &obj) if constexpr (std::is_same_v<typename BaseT::key_type, QString>) { return subMapItem(Map::fromMapRef<typename BaseT::mapped_type>( pathFromOwner().appendComponent(c), obj, - [](DomItem &map, const PathEls::PathComponent &p, - typename BaseT::mapped_type &el) { return map.wrap(p, el); })); + [](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()?)"); } @@ -1913,8 +2171,8 @@ DomItem DomItem::wrap(const PathEls::PathComponent &c, T &obj) if constexpr (IsDomObject<typename BaseT::value_type>::value) { return subListItem(List::fromQListRef<typename BaseT::value_type>( pathFromOwner().appendComponent(c), obj, - [](DomItem &list, const PathEls::PathComponent &p, - typename BaseT::value_type &el) { return list.wrap(p, el); })); + [](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(); @@ -1927,14 +2185,14 @@ DomItem DomItem::wrap(const PathEls::PathComponent &c, T &obj) } template<typename T> -bool DomItem::dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) +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(DomItem &self, DirectVisitor v) +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) { @@ -1945,10 +2203,10 @@ bool ListPT<T>::iterateDirectSubpaths(DomItem &self, DirectVisitor v) } template<typename T> -DomItem ListPT<T>::index(DomItem &self, index_type index) const +DomItem ListPT<T>::index(const DomItem &self, index_type index) const { if (index >= 0 && index < m_pList.size()) - return self.wrap(PathEls::Index(index), *reinterpret_cast<T *>(m_pList.value(index))); + return self.wrap(PathEls::Index(index), *static_cast<const T *>(m_pList.value(index))); return DomItem(); } @@ -1958,13 +2216,13 @@ inline DomKind DomBase::domKind() const return kind2domKind(kind()); } -inline bool DomBase::iterateDirectSubpathsConst(DomItem &self, DirectVisitor visitor) const +inline bool DomBase::iterateDirectSubpathsConst(const DomItem &self, DirectVisitor visitor) const { Q_ASSERT(self.base() == this); - return self.iterateDirectSubpaths(visitor); + return self.iterateDirectSubpaths(std::move(visitor)); } -inline DomItem DomBase::containingObject(DomItem &self) const +inline DomItem DomBase::containingObject(const DomItem &self) const { Path path = pathFromOwner(self); DomItem base = self.owner(); @@ -1986,7 +2244,7 @@ inline QString DomBase::typeName() const return domTypeToString(kind()); } -inline QList<QString> DomBase::fields(DomItem &self) const +inline QList<QString> DomBase::fields(const DomItem &self) const { QList<QString> res; self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { @@ -1997,7 +2255,7 @@ inline QList<QString> DomBase::fields(DomItem &self) const return res; } -inline DomItem DomBase::field(DomItem &self, QStringView name) const +inline DomItem DomBase::field(const DomItem &self, QStringView name) const { DomItem res; self.iterateDirectSubpaths( @@ -2011,7 +2269,7 @@ inline DomItem DomBase::field(DomItem &self, QStringView name) const return res; } -inline index_type DomBase::indexes(DomItem &self) const +inline index_type DomBase::indexes(const DomItem &self) const { index_type res = 0; self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { @@ -2025,7 +2283,7 @@ inline index_type DomBase::indexes(DomItem &self) const return res; } -inline DomItem DomBase::index(DomItem &self, qint64 index) const +inline DomItem DomBase::index(const DomItem &self, qint64 index) const { DomItem res; self.iterateDirectSubpaths( @@ -2039,7 +2297,7 @@ inline DomItem DomBase::index(DomItem &self, qint64 index) const return res; } -inline QSet<QString> const DomBase::keys(DomItem &self) const +inline QSet<QString> const DomBase::keys(const DomItem &self) const { QSet<QString> res; self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { @@ -2050,7 +2308,7 @@ inline QSet<QString> const DomBase::keys(DomItem &self) const return res; } -inline DomItem DomBase::key(DomItem &self, QString name) const +inline DomItem DomBase::key(const DomItem &self, const QString &name) const { DomItem res; self.iterateDirectSubpaths( @@ -2064,12 +2322,12 @@ inline DomItem DomBase::key(DomItem &self, QString name) const return res; } -inline DomItem DomItem::subListItem(const List &list) +inline DomItem DomItem::subListItem(const List &list) const { return DomItem(m_top, m_owner, m_ownerPath, list); } -inline DomItem DomItem::subMapItem(const Map &map) +inline DomItem DomItem::subMapItem(const Map &map) const { return DomItem(m_top, m_owner, m_ownerPath, map); } diff --git a/src/qmldom/qqmldomlinewriter.cpp b/src/qmldom/qqmldomlinewriter.cpp index 3200f4f9c5..33326cb01a 100644 --- a/src/qmldom/qqmldomlinewriter.cpp +++ b/src/qmldom/qqmldomlinewriter.cpp @@ -50,8 +50,9 @@ void PendingSourceLocation::commit() updater(value); } -LineWriter::LineWriter(SinkF innerSink, QString fileName, const LineWriterOptions &options, - int lineNr, int columnNr, int utf16Offset, QString currentLine) +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), @@ -320,7 +321,7 @@ void LineWriter::handleTrailingSpace(LineWriterOptions::TrailingSpace trailingSp } } -void LineWriter::reindentAndSplit(QString eol, bool eof) +void LineWriter::reindentAndSplit(const QString &eol, bool eof) { // maybe write out if (!eol.isEmpty() || eof) { @@ -370,7 +371,7 @@ void LineWriter::textAddCallback(LineWriter::TextAddType t) } } -void LineWriter::commitLine(QString eol, TextAddType tType, int untilChar) +void LineWriter::commitLine(const QString &eol, TextAddType tType, int untilChar) { if (untilChar == -1) untilChar = m_currentLine.size(); diff --git a/src/qmldom/qqmldomlinewriter_p.h b/src/qmldom/qqmldomlinewriter_p.h index eb858f5b7d..86da8d6f54 100644 --- a/src/qmldom/qqmldomlinewriter_p.h +++ b/src/qmldom/qqmldomlinewriter_p.h @@ -88,7 +88,11 @@ public: 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; @@ -100,7 +104,8 @@ public: }; Q_DECLARE_OPERATORS_FOR_FLAGS(LineWriterOptions::Updates) -using PendingSourceLocationId = QAtomicInt; +using PendingSourceLocationId = int; +using PendingSourceLocationIdAtomic = QAtomicInt; class LineWriter; class QMLDOM_EXPORT PendingSourceLocation @@ -132,9 +137,9 @@ public: Eof }; - LineWriter(SinkF innerSink, QString fileName, + LineWriter(const SinkF &innerSink, const QString &fileName, const LineWriterOptions &options = LineWriterOptions(), int lineNr = 0, - int columnNr = 0, int utf16Offset = 0, QString currentLine = QString()); + int columnNr = 0, int utf16Offset = 0, const QString ¤tLine = QString()); std::function<void(QStringView)> sink() { return [this](QStringView s) { this->write(s); }; @@ -143,7 +148,7 @@ public: virtual ~LineWriter() { } QList<SinkF> innerSinks() { return m_innerSinks; } - void addInnerSink(SinkF s) { m_innerSinks.append(s); } + 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); @@ -166,7 +171,7 @@ public: endSourceLocation(pLoc); return *this; } - void commitLine(QString eol, TextAddType t = TextAddType::Normal, int untilChar = -1); + void commitLine(const QString &eol, TextAddType t = TextAddType::Normal, int untilChar = -1); void flush(); void eof(bool ensureNewline = true); SourceLocation committedLocation() const; @@ -183,7 +188,7 @@ public: const QString ¤tLine() const { return m_currentLine; } const LineWriterOptions &options() const { return m_options; } virtual void lineChanged() { } - virtual void reindentAndSplit(QString eol, bool eof = false); + virtual void reindentAndSplit(const QString &eol, bool eof = false); virtual void willCommit() { } private: @@ -205,7 +210,7 @@ protected: int m_utf16Offset = 0; // utf16 offset since start for committed data QString m_currentLine; LineWriterOptions m_options; - PendingSourceLocationId m_lastSourceLocationId; + PendingSourceLocationIdAtomic m_lastSourceLocationId; QMap<PendingSourceLocationId, PendingSourceLocation> m_pendingSourceLocations; QAtomicInt m_lastCallbackId; QMap<int, std::function<bool(LineWriter &, TextAddType)>> m_textAddCallbacks; diff --git a/src/qmldom/qqmldommock.cpp b/src/qmldom/qqmldommock.cpp index 84f9452f9d..df49b9baaf 100644 --- a/src/qmldom/qqmldommock.cpp +++ b/src/qmldom/qqmldommock.cpp @@ -32,11 +32,11 @@ std::pair<QString, MockObject> MockObject::asStringPair() const return std::make_pair(pathFromOwner().last().headName(), *this); } -bool MockObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool MockObject::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { static QHash<QString, QString> knownFields; static QBasicMutex m; - auto toField = [](QString f) -> QStringView { + auto toField = [](const QString &f) -> QStringView { QMutexLocker l(&m); if (!knownFields.contains(f)) knownFields[f] = f; @@ -60,7 +60,7 @@ bool MockObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -std::shared_ptr<OwningItem> MockOwner::doCopy(DomItem &) const +std::shared_ptr<OwningItem> MockOwner::doCopy(const DomItem &) const { return std::make_shared<MockOwner>(*this); } @@ -77,21 +77,21 @@ MockOwner::MockOwner(const MockOwner &o) } } -std::shared_ptr<MockOwner> MockOwner::makeCopy(DomItem &self) const +std::shared_ptr<MockOwner> MockOwner::makeCopy(const DomItem &self) const { return std::static_pointer_cast<MockOwner>(doCopy(self)); } -Path MockOwner::canonicalPath(DomItem &) const +Path MockOwner::canonicalPath(const DomItem &) const { return pathFromTop; } -bool MockOwner::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool MockOwner::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { static QHash<QString, QString> knownFields; static QBasicMutex m; - auto toField = [](QString f) -> QStringView { + auto toField = [](const QString &f) -> QStringView { QMutexLocker l(&m); if (!knownFields.contains(f)) knownFields[f] = f; diff --git a/src/qmldom/qqmldommock_p.h b/src/qmldom/qqmldommock_p.h index b35d4d2e1c..97504cc631 100644 --- a/src/qmldom/qqmldommock_p.h +++ b/src/qmldom/qqmldommock_p.h @@ -43,7 +43,7 @@ public: constexpr static DomType kindValue = DomType::MockObject; DomType kind() const override { return kindValue; } - MockObject(Path pathFromOwner = Path(), QMap<QString, MockObject> subObjects = {}, + MockObject(const Path &pathFromOwner = Path(), QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {}) : CommentableDomElement(pathFromOwner), subObjects(subObjects), subValues(subValues) { @@ -52,7 +52,7 @@ public: MockObject copy() const; std::pair<QString, MockObject> asStringPair() const; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; QMap<QString, MockObject> subObjects; QMap<QString, QCborValue> subValues; @@ -62,13 +62,13 @@ public: class MockOwner final : public OwningItem { protected: - std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; public: constexpr static DomType kindValue = DomType::MockOwner; DomType kind() const override { return kindValue; } - MockOwner(Path pathFromTop = Path(), int derivedFrom = 0, + 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 = {}, @@ -83,7 +83,7 @@ public: { } - MockOwner(Path pathFromTop, int derivedFrom, QDateTime dataRefreshedAt, + MockOwner(const Path &pathFromTop, int derivedFrom, QDateTime dataRefreshedAt, QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {}) : OwningItem(derivedFrom, dataRefreshedAt), pathFromTop(pathFromTop), @@ -94,10 +94,10 @@ public: MockOwner(const MockOwner &o); - std::shared_ptr<MockOwner> makeCopy(DomItem &self) const; - Path canonicalPath(DomItem &self) const override; + std::shared_ptr<MockOwner> makeCopy(const DomItem &self) const; + Path canonicalPath(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; Path pathFromTop; QMap<QString, MockObject> subObjects; diff --git a/src/qmldom/qqmldommoduleindex.cpp b/src/qmldom/qqmldommoduleindex.cpp index 3ba7814189..d44c9ae003 100644 --- a/src/qmldom/qqmldommoduleindex.cpp +++ b/src/qmldom/qqmldommoduleindex.cpp @@ -2,6 +2,7 @@ // 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> @@ -26,7 +27,7 @@ static ErrorGroups myExportErrors() return res; } -bool ModuleScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool ModuleScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && self.dvValueField(visitor, Fields::uri, uri); @@ -35,19 +36,19 @@ bool ModuleScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) int minorVersion = version.minorVersion; return self.subMapItem(Map( self.pathFromOwner().field(Fields::exports), - [minorVersion](DomItem &mapExp, QString name) -> DomItem { + [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, - [](DomItem &, const PathEls::PathComponent &, DomItem &el) { + [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) { return el; }, ListOptions::Normal)); }, - [](DomItem &mapExp) { + [](const DomItem &mapExp) { DomItem mapExpOw = mapExp.owner(); return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw); }, @@ -57,11 +58,11 @@ bool ModuleScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) Path basePath = Path::Current(PathCurrent::Obj).field(Fields::exports); return self.subMapItem(Map( self.pathFromOwner().field(Fields::symbols), - [basePath](DomItem &mapExp, QString name) -> DomItem { + [basePath](const DomItem &mapExp, const QString &name) -> DomItem { QList<Path> symb({ basePath.key(name) }); return mapExp.subReferencesItem(PathEls::Key(name), symb); }, - [](DomItem &mapExp) { + [](const DomItem &mapExp) { DomItem mapExpOw = mapExp.owner(); return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw); }, @@ -73,7 +74,7 @@ bool ModuleScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -std::shared_ptr<OwningItem> ModuleIndex::doCopy(DomItem &) const +std::shared_ptr<OwningItem> ModuleIndex::doCopy(const DomItem &) const { return std::make_shared<ModuleIndex>(*this); } @@ -113,14 +114,14 @@ ModuleIndex::~ModuleIndex() } } -bool ModuleIndex::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +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), - [](DomItem &map, QString minorVersionStr) { + [](const DomItem &map, const QString &minorVersionStr) { bool ok; int minorVersion = minorVersionStr.toInt(&ok); if (minorVersionStr.isEmpty() @@ -130,7 +131,7 @@ bool ModuleIndex::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return DomItem(); return map.copy(map.ownerAs<ModuleIndex>()->ensureMinorVersion(minorVersion)); }, - [this](DomItem &) { + [this](const DomItem &) { QSet<QString> res; for (int el : minorVersions()) if (el >= 0) @@ -150,7 +151,7 @@ bool ModuleIndex::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -QSet<QString> ModuleIndex::exportNames(DomItem &self) const +QSet<QString> ModuleIndex::exportNames(const DomItem &self) const { QSet<QString> res; QList<Path> mySources = sources(); @@ -161,7 +162,7 @@ QSet<QString> ModuleIndex::exportNames(DomItem &self) const return res; } -QList<DomItem> ModuleIndex::autoExports(DomItem &self) const +QList<DomItem> ModuleIndex::autoExports(const DomItem &self) const { QList<DomItem> res; Path selfPath = canonicalPath(self).field(Fields::autoExports); @@ -179,7 +180,7 @@ QList<DomItem> ModuleIndex::autoExports(DomItem &self) const DomItem env = self.environment(); if (!cachedPaths.isEmpty()) { bool outdated = false; - for (Path p : cachedPaths) { + for (const Path &p : cachedPaths) { DomItem newEl = env.path(p); if (!newEl) { outdated = true; @@ -199,9 +200,9 @@ QList<DomItem> ModuleIndex::autoExports(DomItem &self) const QList<Path> mySources = sources(); QSet<QString> knownAutoImportUris; QList<ModuleAutoExport> knownExports; - for (Path p : mySources) { + for (const Path &p : mySources) { DomItem autoExports = self.path(p).field(Fields::autoExports); - for (DomItem i : autoExports.values()) { + for (const DomItem &i : autoExports.values()) { if (const ModuleAutoExport *iPtr = i.as<ModuleAutoExport>()) { if (!knownAutoImportUris.contains(iPtr->import.uri.toString()) || !knownExports.contains(*iPtr)) { @@ -218,7 +219,7 @@ QList<DomItem> ModuleIndex::autoExports(DomItem &self) const return res; } -QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(DomItem &self, QString name, +QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(const DomItem &self, const QString &name, int minorVersion) const { Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion)) @@ -245,12 +246,12 @@ QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(DomItem &self, QStrin undef.append(exportItem); } else { if (majorVersion() < 0) - self.addError(myVersioningErrors() + 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)); + .withPath(myPath))); if ((versionPtr->majorVersion == majorVersion() || versionPtr->majorVersion == Version::Undefined) && versionPtr->minorVersion >= vNow @@ -265,11 +266,11 @@ QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(DomItem &self, QStrin } if (!undef.isEmpty()) { if (!res.isEmpty()) { - self.addError(myVersioningErrors() + 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)); + .withPath(myPath))); return res + undef; } else { return undef; @@ -296,8 +297,8 @@ ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion) minorVersion = Version::Latest; { QMutexLocker l(mutex()); - auto it = m_moduleScope.find(minorVersion); - if (it != m_moduleScope.end()) + auto it = m_moduleScope.constFind(minorVersion); + if (it != m_moduleScope.cend()) return *it; } ModuleScope *res = nullptr; @@ -305,8 +306,8 @@ ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion) auto cleanup = qScopeGuard([&newScope] { delete newScope; }); { QMutexLocker l(mutex()); - auto it = m_moduleScope.find(minorVersion); - if (it != m_moduleScope.end()) { + auto it = m_moduleScope.constFind(minorVersion); + if (it != m_moduleScope.cend()) { res = *it; } else { res = newScope; @@ -317,7 +318,7 @@ ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion) return res; } -void ModuleIndex::mergeWith(std::shared_ptr<ModuleIndex> o) +void ModuleIndex::mergeWith(const std::shared_ptr<ModuleIndex> &o) { if (o) { QList<Path> qmltypesPaths; @@ -329,7 +330,7 @@ void ModuleIndex::mergeWith(std::shared_ptr<ModuleIndex> o) } { QMutexLocker l(mutex()); - for (Path qttPath : qmltypesPaths) { + for (const Path &qttPath : qmltypesPaths) { if (!m_qmltypesFilesPaths.contains((qttPath))) m_qmltypesFilesPaths.append(qttPath); } @@ -343,7 +344,7 @@ void ModuleIndex::mergeWith(std::shared_ptr<ModuleIndex> o) } } -QList<Path> ModuleIndex::qmldirsToLoad(DomItem &self) +QList<Path> ModuleIndex::qmldirsToLoad(const DomItem &self) { // this always checks the filesystem to the qmldir file to load DomItem env = self.environment(); @@ -355,10 +356,15 @@ QList<Path> ModuleIndex::qmldirsToLoad(DomItem &self) + QLatin1String("/qmldir"); QString dirPath; if (majorVersion() >= 0) { - for (QString path : envPtr->loadPaths()) { + 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; @@ -366,10 +372,14 @@ QList<Path> ModuleIndex::qmldirsToLoad(DomItem &self) } } if (dirPath.isEmpty()) { - for (QString path : envPtr->loadPaths()) { + 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; @@ -380,10 +390,14 @@ QList<Path> ModuleIndex::qmldirsToLoad(DomItem &self) QMutexLocker l(mutex()); m_qmldirPaths = QList<Path>({ Paths::qmldirFilePath(dirPath) }); } else if (uri() != u"QML") { - addErrorLocal(myExportErrors() - .warning(tr("Failed to find main qmldir file for %1 %2") - .arg(uri(), QString::number(majorVersion()))) - .handle()); + 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(); } diff --git a/src/qmldom/qqmldommoduleindex_p.h b/src/qmldom/qqmldommoduleindex_p.h index 1d4d9be1df..f2b2731624 100644 --- a/src/qmldom/qqmldommoduleindex_p.h +++ b/src/qmldom/qqmldommoduleindex_p.h @@ -28,7 +28,7 @@ public: constexpr static DomType kindValue = DomType::ModuleScope; DomType kind() const override { return kindValue; } - ModuleScope(QString uri = QString(), const Version &version = Version()) + ModuleScope(const QString &uri = QString(), const Version &version = Version()) : uri(uri), version(version) { } @@ -38,12 +38,12 @@ public: return Path::Field(Fields::moduleScope) .key(version.isValid() ? QString::number(version.minorVersion) : QString()); } - Path pathFromOwner(DomItem &) const override { return pathFromOwner(); } - Path canonicalPath(DomItem &self) const override + Path pathFromOwner(const DomItem &) const override { return pathFromOwner(); } + Path canonicalPath(const DomItem &self) const override { return self.owner().canonicalPath().path(pathFromOwner()); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; QString uri; Version version; @@ -54,15 +54,16 @@ class QMLDOM_EXPORT ModuleIndex final : public OwningItem Q_DECLARE_TR_FUNCTIONS(ModuleIndex); protected: - std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + 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(QString uri, int majorVersion, int derivedFrom = 0, - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + 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) { } @@ -71,21 +72,21 @@ public: ~ModuleIndex(); - std::shared_ptr<ModuleIndex> makeCopy(DomItem &self) const + std::shared_ptr<ModuleIndex> makeCopy(const DomItem &self) const { return std::static_pointer_cast<ModuleIndex>(doCopy(self)); } - Path canonicalPath(DomItem &) const override + Path canonicalPath(const DomItem &) const override { return Paths::moduleIndexPath(uri(), majorVersion()); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; - QSet<QString> exportNames(DomItem &self) const; + QSet<QString> exportNames(const DomItem &self) const; - QList<DomItem> exportsWithNameAndMinorVersion(DomItem &self, QString name, + QList<DomItem> exportsWithNameAndMinorVersion(const DomItem &self, const QString &name, int minorVersion) const; QString uri() const { return m_uri; } @@ -98,15 +99,15 @@ public: return m_moduleScope.keys(); } ModuleScope *ensureMinorVersion(int minorVersion); - void mergeWith(std::shared_ptr<ModuleIndex> o); - void addQmltypeFilePath(Path p) + 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(DomItem &self); + QList<Path> qmldirsToLoad(const DomItem &self); QList<Path> qmltypesFilesPaths() const { QMutexLocker l(mutex()); @@ -122,7 +123,7 @@ public: QMutexLocker l(mutex()); return m_directoryPaths; } - QList<DomItem> autoExports(DomItem &self) const; + QList<DomItem> autoExports(const DomItem &self) const; private: QString m_uri; diff --git a/src/qmldom/qqmldomoutwriter.cpp b/src/qmldom/qqmldomoutwriter.cpp index 7bdbf505e2..bcfc8ace69 100644 --- a/src/qmldom/qqmldomoutwriter.cpp +++ b/src/qmldom/qqmldomoutwriter.cpp @@ -15,14 +15,13 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -OutWriterState::OutWriterState(Path itCanonicalPath, DomItem &it, FileLocations::Tree fLoc) +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; - fLoc->info().ensureCommentLocations(pendingComments.keys()); - } + if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>()) + pendingComments = cRegionsPtr->regionComments(); } void OutWriterState::closeState(OutWriter &w) @@ -50,7 +49,7 @@ OutWriterState &OutWriter::state(int i) return states[states.size() - 1 - i]; } -void OutWriter::itemStart(DomItem &it) +void OutWriter::itemStart(const DomItem &it) { if (!topLocation->path()) topLocation->setPath(it.canonicalPath()); @@ -74,135 +73,304 @@ void OutWriter::itemStart(DomItem &it) if (updateLocs) state().fullRegionId = lineWriter.startSourceLocation( [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(newFLoc, l); }); - regionStart(QString()); + regionStart(MainRegion); } -void OutWriter::itemEnd(DomItem &it) +void OutWriter::itemEnd(const DomItem &it) { Q_ASSERT(states.size() > 0); Q_ASSERT(state().item == it); - regionEnd(QString()); + regionEnd(MainRegion); state().closeState(*this); states.removeLast(); } -void OutWriter::regionStart(QString rName) +void OutWriter::regionStart(FileLocationRegion region) { - Q_ASSERT(!state().pendingRegions.contains(rName)); + Q_ASSERT(!state().pendingRegions.contains(region)); FileLocations::Tree fMap = state().currentMap; - if (!skipComments && state().pendingComments.contains(rName)) { + if (!skipComments && state().pendingComments.contains(region)) { bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations; QList<SourceLocation> *cLocs = - (updateLocs ? &(fMap->info().preCommentLocations[rName]) : nullptr); - state().pendingComments[rName].writePre(*this, cLocs); + (updateLocs ? &(fMap->info().preCommentLocations[region]) : nullptr); + state().pendingComments[region].writePre(*this, cLocs); } - state().pendingRegions[rName] = lineWriter.startSourceLocation( - [rName, fMap](SourceLocation l) { FileLocations::addRegion(fMap, rName, l); }); + state().pendingRegions[region] = lineWriter.startSourceLocation( + [region, fMap](SourceLocation l) { FileLocations::addRegion(fMap, region, l); }); } -void OutWriter::regionEnd(QString rName) +void OutWriter::regionEnd(FileLocationRegion region) { - Q_ASSERT(state().pendingRegions.contains(rName)); + Q_ASSERT(state().pendingRegions.contains(region)); FileLocations::Tree fMap = state().currentMap; - lineWriter.endSourceLocation(state().pendingRegions.value(rName)); - state().pendingRegions.remove(rName); - if (state().pendingComments.contains(rName)) { + 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[rName]) : nullptr); - state().pendingComments[rName].writePost(*this, cLocs); + (updateLocs ? &(fMap->info().postCommentLocations[region]) : nullptr); + state().pendingComments[region].writePost(*this, cLocs); } - state().pendingComments.remove(rName); + state().pendingComments.remove(region); } } -OutWriter &OutWriter::writeRegion(QString rName, QStringView toWrite) +/*! +\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) { - regionStart(rName); + 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; + case IfKeywordRegion: + codeForRegion = u"if"_s; + break; + case SwitchKeywordRegion: + codeForRegion = u"switch"_s; + break; + case YieldKeywordRegion: + codeForRegion = u"yield"_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(rName); + 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. -DomItem OutWriter::updatedFile(DomItem &qmlFile) + 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) { - Q_ASSERT(qmlFile.internalKind() == DomType::QmlFile); - if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) { - std::shared_ptr<QmlFile> copyPtr = qmlFilePtr->makeCopy(qmlFile); - DomItem env = qmlFile.environment(); - std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); - Q_ASSERT(envPtr); - auto newEnvPtr = std::make_shared<DomEnvironment>( - envPtr, envPtr->loadPaths(), envPtr->options()); - newEnvPtr->addQmlFile(copyPtr); - MutableDomItem copy = MutableDomItem(DomItem(newEnvPtr).copy(copyPtr)); - FileLocations::Tree newLoc = topLocation; - Path qmlFilePath = qmlFile.canonicalPath(); - if (newLoc->path() != qmlFilePath) { - if (newLoc->path()) { - if (newLoc->path().length() > qmlFilePath.length() - && newLoc->path().mid(0, qmlFilePath.length()) == qmlFilePath) { - newLoc = FileLocations::createTree(qmlFilePath); - FileLocations::Tree loc = - FileLocations::ensure(newLoc, newLoc->path().mid(qmlFilePath.length()), - AttachedInfo::PathType::Relative); - loc->setSubItems(topLocation->subItems()); - } else { - qCWarning(writeOutLog) - << "failed to base fileLocations in OutWriter (" << newLoc->path() - << ") to current file (" << qmlFilePath << ")"; - } - } else { - newLoc = FileLocations::createTree(qmlFilePath); - Q_ASSERT(newLoc->subItems().isEmpty() && newLoc->info().regions.isEmpty()); + 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); } } - copyPtr->setFileLocationsTree(newLoc); - UpdatedScriptExpression::visitTree( - reformattedScriptExpressions, - [©, qmlFilePath](Path p, UpdatedScriptExpression::Tree t) { - if (std::shared_ptr<ScriptExpression> exprPtr = t->info().expr) { - Q_ASSERT(p.mid(0, qmlFilePath.length()) == qmlFilePath); - MutableDomItem targetExpr = copy.path(p.mid(qmlFilePath.length())); - if (!targetExpr) - qCWarning(writeOutLog) << "failed to get" << p.mid(qmlFilePath.length()) - << "from" << copy.canonicalPath(); - else if (exprPtr->ast() - || (!targetExpr.as<ScriptExpression>() - || !targetExpr.as<ScriptExpression>()->ast())) - targetExpr.setScript(exprPtr); - else { - qCWarning(writeOutLog).noquote() - << "Skipped update of reformatted ScriptExpression with " - "code:\n---------------\n" - << exprPtr->code() << "\n---------------\n preCode:" << - [exprPtr](Sink s) { sinkEscaped(s, exprPtr->preCode()); } - << "\n postCode: " << - [exprPtr](Sink s) { sinkEscaped(s, exprPtr->postCode()); } - << "\n as it failed standalone reparse with errors:" << - [&targetExpr, exprPtr](Sink s) { - targetExpr.item() - .copy(exprPtr, targetExpr.canonicalPath()) - .iterateErrors( - [s](DomItem, ErrorMessage msg) { - s(u"\n "); - msg.dump(s); - return true; - }, - true); - } - << "\n"; - } - } - return true; - }); - return copy.item(); - } - return DomItem(); + 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 index 81251e60be..8b00223ea2 100644 --- a/src/qmldom/qqmldomoutwriter_p.h +++ b/src/qmldom/qqmldomoutwriter_p.h @@ -27,16 +27,10 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -#define QMLDOM_USTRING(s) u##s -#define QMLDOM_REGION(name) constexpr const auto name = QMLDOM_USTRING(#name) -// namespace, so it cam be reopened to add more entries -namespace Regions { -} // namespace Regions - class QMLDOM_EXPORT OutWriterState { public: - OutWriterState(Path itPath, DomItem &it, FileLocations::Tree fLoc); + OutWriterState(const Path &itPath, const DomItem &it, const FileLocations::Tree &fLoc); void closeState(OutWriter &); @@ -44,8 +38,8 @@ public: DomItem item; PendingSourceLocationId fullRegionId; FileLocations::Tree currentMap; - QMap<QString, PendingSourceLocationId> pendingRegions; - QMap<QString, CommentedElement> pendingComments; + QMap<FileLocationRegion, PendingSourceLocationId> pendingRegions; + QMap<FileLocationRegion, CommentedElement> pendingComments; }; class QMLDOM_EXPORT OutWriter @@ -92,21 +86,14 @@ public: return indent; } - void itemStart(DomItem &it); - void itemEnd(DomItem &it); - void regionStart(QString rName); - void regionStart(QStringView rName) { regionStart(rName.toString()); } - void regionEnd(QString rName); - void regionEnd(QStringView rName) { regionEnd(rName.toString()); } + 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(QString rName, QStringView toWrite); - OutWriter &writeRegion(QStringView rName, QStringView toWrite) - { - return writeRegion(rName.toString(), toWrite); - } - OutWriter &writeRegion(QString t) { return writeRegion(t, t); } - OutWriter &writeRegion(QStringView t) { return writeRegion(t.toString(), t); } + OutWriter &writeRegion(FileLocationRegion region, QStringView toWrite); + OutWriter &writeRegion(FileLocationRegion region); OutWriter &ensureNewline(int nNewlines = 1) { lineWriter.ensureNewline(nNewlines); @@ -153,14 +140,21 @@ public: return lineWriter.addTextAddCallback(callback); } bool removeTextAddCallback(int i) { return lineWriter.removeTextAddCallback(i); } - void addReformattedScriptExpression(Path p, std::shared_ptr<ScriptExpression> exp) + 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 updatedFile(DomItem &qmlFile); + 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 diff --git a/src/qmldom/qqmldompath.cpp b/src/qmldom/qqmldompath.cpp index 24eb9768eb..d2b4582bc0 100644 --- a/src/qmldom/qqmldompath.cpp +++ b/src/qmldom/qqmldompath.cpp @@ -74,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); } @@ -100,9 +101,6 @@ enum class ParserState{ End }; -PathComponent::~PathComponent(){ -} - int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) { int k1 = static_cast<int>(p1.kind()); @@ -115,19 +113,19 @@ 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: { - PathRoot k1 = p1.data.root.contextKind; - PathRoot k2 = 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) @@ -135,23 +133,26 @@ int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) 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; @@ -213,7 +214,7 @@ PathIterator Path::end() const PathRoot Path::headRoot() const { auto &comp = component(0); - if (PathEls::Root const * r = comp.base()->asRoot()) + if (PathEls::Root const * r = comp.asRoot()) return r->contextKind; return PathRoot::Other; } @@ -221,7 +222,7 @@ PathRoot Path::headRoot() const PathCurrent Path::headCurrent() const { auto comp = component(0); - if (PathEls::Current const * c = comp.base()->asCurrent()) + if (PathEls::Current const * c = comp.asCurrent()) return c->contextKind; return PathCurrent::Other; } @@ -248,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 (PathEls::Filter const * f = comp.base()->asFilter()) { + if (PathEls::Filter const * f = comp.asFilter()) { return f->filterFunction; } return {}; @@ -279,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())) @@ -288,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())) @@ -297,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(); @@ -525,7 +526,7 @@ Path Path::Root(PathRoot s) 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<PathEls::PathData>( QStringList(s), QVector<Component>(1,Component(PathEls::Root(s))))); @@ -550,7 +551,7 @@ Path Path::Field(QStringView s) 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<PathEls::PathData>( QStringList(s), QVector<Component>(1,Component(PathEls::Field(s))))); @@ -564,7 +565,7 @@ Path Path::Key(QStringView s) 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<PathEls::PathData>( @@ -577,7 +578,7 @@ Path Path::Current(PathCurrent s) 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<PathEls::PathData>( QStringList(s), QVector<Component>(1,Component(PathEls::Current(s))))); @@ -602,7 +603,7 @@ Path Path::empty() const QStringList(), QVector<Component>(1,Component()), m_data)); } -Path Path::field(QString name) const +Path Path::field(const QString &name) const { auto res = field(QStringView(name)); res.m_data->strData.append(name); @@ -617,7 +618,7 @@ Path Path::field(QStringView name) const QStringList(), QVector<Component>(1,Component(PathEls::Field(name))), m_data)); } -Path Path::key(QString name) const +Path Path::key(const QString &name) const { if (m_endOffset != 0) return noEndOffset().key(name); @@ -646,14 +647,14 @@ Path Path::any() const QStringList(), QVector<Component>(1,Component(PathEls::Any())), m_data)); } -Path Path::filter(function<bool (DomItem)> filterF, QString desc) const +Path Path::filter(const function<bool(const DomItem &)> &filterF, const QString &desc) const { auto res = filter(filterF, QStringView(desc)); res.m_data->strData.append(desc); return res; } -Path Path::filter(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().filter(filter, desc); @@ -667,7 +668,7 @@ Path Path::current(PathCurrent s) const QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data)); } -Path Path::current(QString s) const +Path Path::current(const QString &s) const { auto res = current(QStringView(s)); res.m_data->strData.append(s); @@ -682,7 +683,7 @@ Path Path::current(QStringView s) const QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data)); } -Path Path::path(Path toAdd, bool avoidToAddAsBase) const +Path Path::path(const Path &toAdd, bool avoidToAddAsBase) const { if (toAdd.length() == 0) return *this; @@ -808,7 +809,7 @@ int Path::cmp(const Path &p1, const Path &p2) return 0; } -Path::Path(quint16 endOffset, quint16 length, std::shared_ptr<PathEls::PathData> data) +Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data) :m_endOffset(endOffset), m_length(length), m_data(data) { } @@ -869,10 +870,10 @@ Path Path::appendComponent(const PathEls::PathComponent &c) } break; case PathEls::Kind::Filter: - if (!c.base()->asFilter()->filterDescription.isEmpty()) { - my_data->strData.append(c.base()->asFilter()->filterDescription.toString()); + if (!c.asFilter()->filterDescription.isEmpty()) { + my_data->strData.append(c.asFilter()->filterDescription.toString()); my_data->components.append( - PathEls::Filter(c.base()->asFilter()->filterFunction, my_data->strData.last())); + PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last())); } else { my_data->components.append(c); } @@ -900,7 +901,7 @@ ErrorGroups Path::myErrors() 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) { @@ -951,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) diff --git a/src/qmldom/qqmldompath_p.h b/src/qmldom/qqmldompath_p.h index 859e23d1b0..1a5af85e8e 100644 --- a/src/qmldom/qqmldompath_p.h +++ b/src/qmldom/qqmldompath_p.h @@ -69,35 +69,21 @@ 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 final : public Base { public: Empty() = default; - 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; } + 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 final : public Base @@ -105,12 +91,10 @@ 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; }; @@ -120,12 +104,11 @@ 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; } + 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; }; @@ -134,18 +117,16 @@ class Key final : public Base { public: Key() = default; - Key(QString n) : keyValue(n) { } - Kind kind() const override { return Kind::Key; } - QString name() const override { return keyValue; } - bool checkName(QStringView s) const override { return s == keyValue; } - QStringView stringView() const override { return keyValue; } - void dump(Sink sink) const override { + 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; } QString keyValue; }; @@ -164,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"); @@ -185,16 +165,13 @@ 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::Other; QStringView contextName; @@ -214,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()); @@ -243,13 +219,13 @@ 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::Other; QStringView contextName; @@ -259,160 +235,110 @@ class Any final : public Base { public: Any() = default; - 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; } + 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 final : public Base { public: Filter() = default; - 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(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(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) {} + 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; - 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; } @@ -424,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; @@ -437,30 +367,51 @@ public: #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{ 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); @@ -468,20 +419,26 @@ 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); @@ -489,6 +446,7 @@ QMLDOM_FIELD(globalScopeWithName); QMLDOM_FIELD(hasCallback); QMLDOM_FIELD(hasCustomParser); QMLDOM_FIELD(idStr); +QMLDOM_FIELD(identifier); QMLDOM_FIELD(ids); QMLDOM_FIELD(implicit); QMLDOM_FIELD(import); @@ -500,6 +458,7 @@ QMLDOM_FIELD(imports); QMLDOM_FIELD(inProgress); QMLDOM_FIELD(infoItem); QMLDOM_FIELD(inheritVersion); +QMLDOM_FIELD(initializer); QMLDOM_FIELD(interfaceNames); QMLDOM_FIELD(isAlias); QMLDOM_FIELD(isComposite); @@ -519,7 +478,9 @@ 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); @@ -535,17 +496,20 @@ 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); @@ -560,6 +524,7 @@ QMLDOM_FIELD(pragmas); QMLDOM_FIELD(preCode); QMLDOM_FIELD(preCommentLocations); QMLDOM_FIELD(preComments); +QMLDOM_FIELD(properties); QMLDOM_FIELD(propertyDef); QMLDOM_FIELD(propertyDefRef); QMLDOM_FIELD(propertyDefs); @@ -574,7 +539,6 @@ QMLDOM_FIELD(qmldirWithPath); QMLDOM_FIELD(qmltypesFileWithPath); QMLDOM_FIELD(qmltypesFiles); QMLDOM_FIELD(qualifiedImports); -QMLDOM_FIELD(queue); QMLDOM_FIELD(rawComment); QMLDOM_FIELD(read); QMLDOM_FIELD(referredObject); @@ -585,8 +549,13 @@ 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); @@ -598,6 +567,8 @@ 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); @@ -612,7 +583,7 @@ QMLDOM_FIELD(values); QMLDOM_FIELD(version); QMLDOM_FIELD(when); QMLDOM_FIELD(write); -} +} // namespace Fields class Source; size_t qHash(const Path &, size_t); @@ -646,12 +617,12 @@ 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(int n = 1) const; Path dropTail(int n = 1) const; @@ -660,34 +631,35 @@ public: 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 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(QString s); + static Path Root(const QString &s); static Path Index(index_type i); static Path Field(QStringView s=u""); - static Path Field(QString s); + static Path Field(const QString &s); static Path Key(QStringView s=u""); - static Path Key(QString s); + static Path Key(const QString &s); static Path Current(PathCurrent c); static Path Current(QStringView s=u""); - static Path Current(QString s); + static Path Current(const QString &s); static Path Empty(); // add Path empty() const; - Path field(QString name) const; + Path field(const QString &name) const; Path field(QStringView name) const; - Path key(QString name) const; + Path key(const QString &name) const; Path key(QStringView name) const; Path index(index_type i) const; Path any() const; - Path filter(std::function<bool(DomItem)>, QString) const; - Path filter(std::function<bool(DomItem)>, QStringView desc=u"<native code filter>") 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(QString s) const; + Path current(const QString &s) const; Path current(QStringView s=u"") const; - Path path(Path toAdd, bool avoidToAddAsBase = false) const; + Path path(const Path &toAdd, bool avoidToAddAsBase = false) const; Path expandFront() const; Path expandBack() const; @@ -706,7 +678,8 @@ public: private: const Component &component(int i) const; - explicit Path(quint16 endOffset, quint16 length, std::shared_ptr<PathEls::PathData> data); + 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); @@ -776,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 index 9114f39c3e..3093a3d4da 100644 --- a/src/qmldom/qqmldomreformatter.cpp +++ b/src/qmldom/qqmldomreformatter.cpp @@ -11,6 +11,7 @@ #include <QString> +#include <algorithm> #include <limits> QT_BEGIN_NAMESPACE @@ -19,1170 +20,1116 @@ namespace Dom { using namespace AST; -class Rewriter : protected BaseVisitor +bool ScriptFormatter::preVisit(Node *n) { - OutWriter &lw; - std::shared_ptr<AstComments> comments; - std::function<QStringView(SourceLocation)> loc2Str; - QHash<Node *, QList<std::function<void()>>> postOps; - int expressionDepth = 0; - - bool addSemicolons() const { return expressionDepth > 0; } - -public: - Rewriter(OutWriter &lw, std::shared_ptr<AstComments> comments, - std::function<QStringView(SourceLocation)> loc2Str, Node *node) - : lw(lw), comments(comments), loc2Str(loc2Str) - { - accept(node); + if (CommentedElement *c = comments->commentForNode(n)) { + c->writePre(lw); + postOps[n].append([c, this]() { c->writePost(lw); }); } - -protected: - bool preVisit(Node *n) override - { - if (CommentedElement *c = comments->commentForNode(n)) { - c->writePre(lw); - postOps[n].append([c, this]() { c->writePost(lw); }); - } - return true; - } - void postVisit(Node *n) override - { - for (auto &op : postOps[n]) { - op(); - } - postOps.remove(n); + return true; +} +void ScriptFormatter::postVisit(Node *n) +{ + for (auto &op : postOps[n]) { + op(); } + postOps.remove(n); +} - void accept(Node *node) { Node::accept(node, this); } +void ScriptFormatter::lnAcceptIndented(Node *node) +{ + int indent = lw.increaseIndent(1); + lw.ensureNewline(); + accept(node); + lw.decreaseIndent(1, indent); +} - void 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 out(const char *str) { lw.write(QString::fromLatin1(str)); } - - void out(QStringView str) { lw.write(str); } +bool ScriptFormatter::visit(ThisExpression *ast) +{ + out(ast->thisToken); + return true; +} - void out(const SourceLocation &loc) - { - if (loc.length != 0) - out(loc2Str(loc)); - } +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; +} - void newLine() { lw.ensureNewline(); } +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 acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline = false) - { - 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; +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; +} - // we are not supposed to handle the ui - bool visit(UiPragma *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiEnumDeclaration *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiEnumMemberList *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiImport *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiObjectDefinition *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiObjectInitializer *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiParameterList *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiPublicMember *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiObjectBinding *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiScriptBinding *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiArrayBinding *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiHeaderItemList *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiObjectMemberList *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiArrayMemberList *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiQualifiedId *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiProgram *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiSourceElement *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiVersionSpecifier *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiInlineComponent *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiAnnotation *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiAnnotationList *) override - { - Q_ASSERT(false); - return false; - } - bool visit(UiRequired *) override - { - Q_ASSERT(false); - 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 visit(ThisExpression *ast) override - { - out(ast->thisToken); - return true; - } - bool visit(NullExpression *ast) override - { - out(ast->nullToken); - return true; - } - bool visit(TrueLiteral *ast) override - { - out(ast->trueToken); - return true; - } - bool visit(FalseLiteral *ast) override - { - out(ast->falseToken); - return true; - } - bool visit(IdentifierExpression *ast) override - { - out(ast->identifierToken); - return true; - } - bool visit(StringLiteral *ast) override - { - // 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 visit(NumericLiteral *ast) override - { - out(ast->literalToken); - return true; - } - bool visit(RegExpLiteral *ast) override - { - out(ast->literalToken); - return true; - } +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(); - bool visit(ArrayPattern *ast) override - { - out(ast->lbracketToken); - int baseIndent = lw.increaseIndent(1); - if (ast->elements) - accept(ast->elements); - out(ast->commaToken); - lw.decreaseIndent(1, baseIndent); - out(ast->rbracketToken); - return false; + 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 visit(ObjectPattern *ast) override - { - out(ast->lbraceToken); - ++expressionDepth; - if (ast->properties) { - lnAcceptIndented(ast->properties); +bool ScriptFormatter::visit(PatternPropertyList *ast) +{ + for (PatternPropertyList *it = ast; it; it = it->next) { + accept(it->property); + if (it->next) { + out(","); newLine(); } - --expressionDepth; - out(ast->rbraceToken); - return false; } + return false; +} - bool visit(PatternElementList *ast) override - { - for (PatternElementList *it = ast; it; it = it->next) { - if (it->elision) - accept(it->elision); - if (it->elision && it->element) - out(", "); - if (it->element) - accept(it->element); - if (it->next) - out(", "); +// 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("*"); } - return false; - } - - bool visit(PatternPropertyList *ast) override - { - for (PatternPropertyList *it = ast; it; it = it->next) { - PatternProperty *assignment = AST::cast<PatternProperty *>(it->property); - if (assignment) { - preVisit(assignment); - bool isStringLike = AST::cast<StringLiteralPropertyName *>(assignment->name) - || cast<IdentifierPropertyName *>(assignment->name); - if (isStringLike) - out("\""); - accept(assignment->name); - if (isStringLike) - out("\""); - out(": "); // assignment->colonToken - if (it->next) - postOps[assignment->initializer].append([this] { - out(","); // always invalid? - }); - accept(assignment->initializer); - if (it->next) - newLine(); - postVisit(assignment); - continue; - } - PatternPropertyList *getterSetter = AST::cast<PatternPropertyList *>(it->next); - if (getterSetter->property) { - switch (getterSetter->property->type) { - case PatternElement::Getter: - out("get"); - break; - case PatternElement::Setter: - out("set"); - break; - default: - break; - } - - accept(getterSetter->property->name); - out("("); - // accept(getterSetter->formals); // TODO - out(")"); - out(" {"); - // accept(getterSetter->functionBody); // TODO - 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; } - bool visit(NestedExpression *ast) override - { - out(ast->lparenToken); - int baseIndent = lw.increaseIndent(1); - accept(ast->expression); - lw.decreaseIndent(1, baseIndent); - out(ast->rparenToken); - 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); } - bool visit(IdentifierPropertyName *ast) override - { - out(ast->id.toString()); - return true; - } - bool visit(StringLiteralPropertyName *ast) override - { - out(ast->id.toString()); - return true; - } - bool visit(NumericLiteralPropertyName *ast) override - { - out(QString::number(ast->id)); - return true; + if (property->initializer) { + // CoverInitializedName[?Yield] + if (bindingIdentifierExist) { + out(" = "); + useInitializer = true; + } + if (useInitializer) + accept(property->initializer); } + return false; +} - bool visit(TemplateLiteral *ast) override - { - // 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); - } +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; } + accept(ast->expression); + return true; +} - bool visit(ArrayMemberExpression *ast) override - { - 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(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 visit(FieldMemberExpression *ast) override - { - accept(ast->base); - out(ast->dotToken); - out(ast->identifierToken); - return false; - } +bool ScriptFormatter::visit(FieldMemberExpression *ast) +{ + accept(ast->base); + out(ast->dotToken); + out(ast->identifierToken); + return false; +} - bool visit(NewMemberExpression *ast) override - { - out("new "); // ast->newToken - accept(ast->base); - out(ast->lparenToken); - accept(ast->arguments); - out(ast->rparenToken); - 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 visit(NewExpression *ast) override - { - out("new "); // ast->newToken - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(NewExpression *ast) +{ + out("new "); // ast->newToken + accept(ast->expression); + return false; +} - bool visit(CallExpression *ast) override - { - accept(ast->base); - out(ast->lparenToken); - int baseIndent = lw.increaseIndent(1); - accept(ast->arguments); - lw.decreaseIndent(1, baseIndent); - out(ast->rparenToken); - return false; - } +bool ScriptFormatter::visit(CallExpression *ast) +{ + accept(ast->base); + out(ast->lparenToken); + accept(ast->arguments); + out(ast->rparenToken); + return false; +} - bool visit(PostIncrementExpression *ast) override - { - accept(ast->base); - out(ast->incrementToken); - return false; - } +bool ScriptFormatter::visit(PostIncrementExpression *ast) +{ + accept(ast->base); + out(ast->incrementToken); + return false; +} - bool visit(PostDecrementExpression *ast) override - { - accept(ast->base); - out(ast->decrementToken); - return false; - } +bool ScriptFormatter::visit(PostDecrementExpression *ast) +{ + accept(ast->base); + out(ast->decrementToken); + return false; +} - bool visit(PreIncrementExpression *ast) override - { - out(ast->incrementToken); - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(PreIncrementExpression *ast) +{ + out(ast->incrementToken); + accept(ast->expression); + return false; +} - bool visit(PreDecrementExpression *ast) override - { - out(ast->decrementToken); - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(PreDecrementExpression *ast) +{ + out(ast->decrementToken); + accept(ast->expression); + return false; +} - bool visit(DeleteExpression *ast) override - { - out("delete "); // ast->deleteToken - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(DeleteExpression *ast) +{ + out("delete "); // ast->deleteToken + accept(ast->expression); + return false; +} - bool visit(VoidExpression *ast) override - { - out("void "); // ast->voidToken - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(VoidExpression *ast) +{ + out("void "); // ast->voidToken + accept(ast->expression); + return false; +} - bool visit(TypeOfExpression *ast) override - { - out("typeof "); // ast->typeofToken - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(TypeOfExpression *ast) +{ + out("typeof "); // ast->typeofToken + accept(ast->expression); + return false; +} - bool visit(UnaryPlusExpression *ast) override - { - out(ast->plusToken); - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(UnaryPlusExpression *ast) +{ + out(ast->plusToken); + accept(ast->expression); + return false; +} - bool visit(UnaryMinusExpression *ast) override - { - out(ast->minusToken); - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(UnaryMinusExpression *ast) +{ + out(ast->minusToken); + accept(ast->expression); + return false; +} - bool visit(TildeExpression *ast) override - { - out(ast->tildeToken); - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(TildeExpression *ast) +{ + out(ast->tildeToken); + accept(ast->expression); + return false; +} - bool visit(NotExpression *ast) override - { - out(ast->notToken); - accept(ast->expression); - return false; - } +bool ScriptFormatter::visit(NotExpression *ast) +{ + out(ast->notToken); + accept(ast->expression); + return false; +} - bool visit(BinaryExpression *ast) override - { - accept(ast->left); - out(" "); - out(ast->operatorToken); - out(" "); - accept(ast->right); - return false; - } +bool ScriptFormatter::visit(BinaryExpression *ast) +{ + accept(ast->left); + out(" "); + out(ast->operatorToken); + out(" "); + accept(ast->right); + return false; +} - bool visit(ConditionalExpression *ast) override - { - accept(ast->expression); - out(" ? "); // ast->questionToken - accept(ast->ok); - out(" : "); // ast->colonToken - accept(ast->ko); - 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 visit(Block *ast) override - { - out(ast->lbraceToken); +bool ScriptFormatter::visit(Block *ast) +{ + out(ast->lbraceToken); + if (ast->statements) { ++expressionDepth; lnAcceptIndented(ast->statements); newLine(); --expressionDepth; - out(ast->rbraceToken); - return false; } + out(ast->rbraceToken); + return false; +} - bool visit(VariableStatement *ast) override - { - out(ast->declarationKindToken); - out(" "); - accept(ast->declarations); - if (addSemicolons()) - out(";"); - return false; +bool ScriptFormatter::visit(VariableStatement *ast) +{ + out(ast->declarationKindToken); + out(" "); + accept(ast->declarations); + if (addSemicolons()) + out(";"); + return false; +} + +bool ScriptFormatter::visit(PatternElement *ast) +{ + 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; +} - void 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(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 visit(PatternElement *ast) override - { - if (ast->isForDeclaration) { - outputScope(ast->scope); - } - accept(ast->bindingTarget); - 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; +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) { + if (auto pe = ast->declarations->declaration) { + out(pe->declarationKindToken); + out(" "); } - out(ast->identifierToken); - if (ast->initializer) { - if (ast->isVariableDeclaration()) - out(" = "); - accept(ast->initializer); + for (VariableDeclarationList *it = ast->declarations; it; it = it->next) { + accept(it->declaration); } - return false; } + out("; "); // ast->firstSemicolonToken + accept(ast->condition); + out("; "); // ast->secondSemicolonToken + accept(ast->expression); + out(ast->rparenToken); + acceptBlockOrIndented(ast->statement); + return false; +} - bool visit(EmptyStatement *ast) override - { - out(ast->semicolonToken); - return false; +bool ScriptFormatter::visit(ForEachStatement *ast) +{ + out(ast->forToken); + out(" "); + out(ast->lparenToken); + if (auto pe = AST::cast<PatternElement *>(ast->lhs)) { + out(pe->declarationKindToken); + out(" "); } + accept(ast->lhs); + out(" "); + out(ast->inOfToken); + out(" "); + accept(ast->expression); + out(ast->rparenToken); + acceptBlockOrIndented(ast->statement); + return false; +} - bool visit(IfStatement *ast) override - { - out(ast->ifToken); +bool ScriptFormatter::visit(ContinueStatement *ast) +{ + out(ast->continueToken); + if (!ast->label.isNull()) { 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; + out(ast->identifierToken); } + if (addSemicolons()) + out(";"); + return false; +} - bool visit(DoWhileStatement *ast) override - { - out(ast->doToken); - acceptBlockOrIndented(ast->statement, true); - out(ast->whileToken); +bool ScriptFormatter::visit(BreakStatement *ast) +{ + out(ast->breakToken); + if (!ast->label.isNull()) { out(" "); - out(ast->lparenToken); + 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); - out(ast->rparenToken); - return false; } + if (ast->returnToken.length > 0 && addSemicolons()) + out(";"); + return false; +} - bool visit(WhileStatement *ast) override - { - out(ast->whileToken); - out(" "); - out(ast->lparenToken); +bool ScriptFormatter::visit(YieldExpression *ast) +{ + out(ast->yieldToken); + if (ast->isYieldStar) + out("*"); + if (ast->expression) { + if (ast->yieldToken.isValid()) + out(" "); accept(ast->expression); - out(ast->rparenToken); - acceptBlockOrIndented(ast->statement); - return false; } + return false; +} - bool visit(ForStatement *ast) override - { - out(ast->forToken); +bool ScriptFormatter::visit(ThrowStatement *ast) +{ + out(ast->throwToken); + if (ast->expression) { 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; } + if (addSemicolons()) + out(";"); + return false; +} - bool visit(ForEachStatement *ast) override - { - out(ast->forToken); - out(" "); - out(ast->lparenToken); - accept(ast->lhs); +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(" "); - out(ast->inOfToken); + accept(ast->catchExpression); + } + if (ast->finallyExpression) { out(" "); - accept(ast->expression); - out(ast->rparenToken); - acceptBlockOrIndented(ast->statement); - return false; + accept(ast->finallyExpression); } + return false; +} - bool visit(ContinueStatement *ast) override - { - out(ast->continueToken); - if (!ast->label.isNull()) { - out(" "); +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 (addSemicolons()) - out(";"); - return false; } + if (ast->lbraceToken.length != 0) + --expressionDepth; + out(ast->rbraceToken); + return false; +} - bool visit(BreakStatement *ast) override - { - out(ast->breakToken); - if (!ast->label.isNull()) { - out(" "); - out(ast->identifierToken); - } - if (addSemicolons()) - out(";"); - return false; +bool ScriptFormatter::visit(Elision *ast) +{ + for (Elision *it = ast; it; it = it->next) { + if (it->next) + out(", "); // ast->commaToken } + return false; +} - bool visit(ReturnStatement *ast) override - { - out(ast->returnToken); - if (ast->expression) { - if (ast->returnToken.length != 0) - out(" "); - accept(ast->expression); +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 } - if (ast->returnToken.length > 0 && addSemicolons()) - out(";"); - return false; } + return false; +} - bool visit(ThrowStatement *ast) override - { - out(ast->throwToken); - if (ast->expression) { - out(" "); - accept(ast->expression); +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; } - if (addSemicolons()) - out(";"); - return false; - } - bool visit(WithStatement *ast) override - { - out(ast->withToken); - out(" "); - out(ast->lparenToken); - accept(ast->expression); - out(ast->rparenToken); - acceptBlockOrIndented(ast->statement); - return false; + 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 visit(SwitchStatement *ast) override - { - out(ast->switchToken); - out(" "); - out(ast->lparenToken); - accept(ast->expression); - out(ast->rparenToken); - out(" "); - accept(ast->block); - 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 visit(CaseBlock *ast) override - { - out(ast->lbraceToken); - ++expressionDepth; - newLine(); - accept(ast->clauses); - if (ast->clauses && ast->defaultClause) - newLine(); - accept(ast->defaultClause); - if (ast->moreClauses) +bool ScriptFormatter::visit(CaseClauses *ast) +{ + for (CaseClauses *it = ast; it; it = it->next) { + accept(it->clause); + if (it->next) newLine(); - accept(ast->moreClauses); - newLine(); - --expressionDepth; - out(ast->rbraceToken); - return false; } + return false; +} - bool visit(CaseClause *ast) override - { - out("case "); // ast->caseToken - accept(ast->expression); - out(ast->colonToken); - if (ast->statements) - lnAcceptIndented(ast->statements); - 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; +} - bool visit(DefaultClause *ast) override - { - out(ast->defaultToken); - out(ast->colonToken); - lnAcceptIndented(ast->statements); - 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; +} - bool visit(LabelledStatement *ast) override - { - out(ast->identifierToken); - out(": "); // ast->colonToken - accept(ast->statement); - return false; - } +// 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 visit(TryStatement *ast) override - { - 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(AST::ImportDeclaration *ast) +{ + out(ast->importToken); + lw.space(); + if (!ast->moduleSpecifier.isNull()) { + out(ast->moduleSpecifierToken); } + return true; +} - bool visit(Catch *ast) override - { - out(ast->catchToken); - out(" "); - out(ast->lparenToken); +bool ScriptFormatter::visit(AST::ImportSpecifier *ast) +{ + if (!ast->identifier.isNull()) { out(ast->identifierToken); - out(") "); // ast->rparenToken - accept(ast->statement); - return false; + lw.space(); + out("as"); + lw.space(); } + out(ast->importedBindingToken); + return true; +} - bool visit(Finally *ast) override - { - out("finally "); // ast->finallyToken - accept(ast->statement); - return false; - } +bool ScriptFormatter::visit(AST::NameSpaceImport *ast) +{ + out(ast->starToken); + lw.space(); + out("as"); + lw.space(); + out(ast->importedBindingToken); + return true; +} - bool visit(FunctionDeclaration *ast) override - { - return visit(static_cast<FunctionExpression *>(ast)); +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 visit(FunctionExpression *ast) override - { - if (!ast->isArrowFunction) { - out("function "); // ast->functionToken - if (!ast->name.isNull()) - out(ast->identifierToken); - } - out(ast->lparenToken); - if (ast->isArrowFunction && ast->formals && ast->formals->next) - out("("); - int baseIndent = lw.increaseIndent(1); - accept(ast->formals); - lw.decreaseIndent(1, baseIndent); - if (ast->isArrowFunction && ast->formals && ast->formals->next) - 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); - } +bool ScriptFormatter::visit(AST::ImportClause *ast) +{ + if (!ast->importedDefaultBinding.isNull()) { + out(ast->importedDefaultBindingToken); + if (ast->nameSpaceImport || ast->namedImports) { + out(","); + lw.space(); } - if (ast->lbraceToken.length != 0) - --expressionDepth; - out(ast->rbraceToken); - return false; } + return true; +} - bool visit(Elision *ast) override - { - for (Elision *it = ast; it; it = it->next) { - if (it->next) - out(", "); // ast->commaToken - } - return false; +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 visit(ArgumentList *ast) override - { - 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(AST::ExportClause *ast) +{ + out(ast->leftBraceToken); + if (ast->exportsList) { + lw.space(); } + return true; +} - bool visit(StatementList *ast) override - { - ++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; - } +bool ScriptFormatter::visit(AST::ExportSpecifier *ast) +{ + out(ast->identifier); + if (ast->exportedIdentifierToken.isValid()) { + lw.space(); + out("as"); + lw.space(); + out(ast->exportedIdentifier); + } + return true; +} - accept(it->statement); - if (it->next) - newLine(); +bool ScriptFormatter::visit(AST::ExportsList *ast) +{ + for (ExportsList *it = ast; it; it = it->next) { + accept(it->exportSpecifier); + if (it->next) { + out(","); + lw.space(); } - --expressionDepth; - return false; } + return false; +} - bool visit(VariableDeclarationList *ast) override - { - for (VariableDeclarationList *it = ast; it; it = it->next) { - accept(it->declaration); - if (it->next) - out(", "); // it->commaToken - } - return false; - } +bool ScriptFormatter::visit(AST::FromClause *ast) +{ + lw.space(); + out(ast->fromToken); + lw.space(); + out(ast->moduleSpecifierToken); + return true; +} - bool visit(CaseClauses *ast) override - { - for (CaseClauses *it = ast; it; it = it->next) { - accept(it->clause); - if (it->next) - newLine(); - } - return false; - } +void ScriptFormatter::endVisit(ComputedPropertyName *) +{ + out("]"); +} - bool visit(FormalParameterList *ast) override - { - for (FormalParameterList *it = ast; it; it = it->next) { - out(it->element->bindingIdentifier.toString()); // TODO - if (it->next) - out(", "); - } - return false; +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(";"); } - // to check - bool visit(TypeExpression *) override { return true; } - bool visit(SuperLiteral *) override - { - out("super"); - return true; - } - bool visit(PatternProperty *) override { return true; } - bool visit(ComputedPropertyName *) override - { - out("["); - return true; - } - bool visit(TaggedTemplate *) override { return true; } - bool visit(Expression *el) override - { - accept(el->left); - out(", "); - accept(el->right); - return false; - } - bool visit(ExpressionStatement *el) override - { - if (addSemicolons()) - postOps[el->expression].append([this]() { out(";"); }); - return true; + // add a semicolon at the end of the following expressions + // export ExportClause ; + if (ast->exportClause && !ast->fromClause) { + out(";"); } - bool visit(YieldExpression *) override { return true; } - bool visit(ClassExpression *) override { return true; } - - // Return false because we want to omit default function calls in accept0 implementation. - bool visit(ClassDeclaration *ast) override - { - preVisit(ast); - out(ast->classToken); - out(" "); - out(ast->name); - if (ast->heritage) { - out(" extends "); - accept(ast->heritage); + + // 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(";"); } - out(" {"); - int baseIndent = lw.increaseIndent(); - for (ClassElementList *it = ast->elements; it; it = it->next) { - PatternProperty *property = it->property; - lw.newline(); - preVisit(property); - if (it->isStatic) - out("static "); - if (property->type == PatternProperty::Getter) - out("get "); - else if (property->type == PatternProperty::Setter) - out("set "); - FunctionExpression *f = AST::cast<FunctionExpression *>(property->initializer); - const bool scoped = f->lbraceToken.length != 0; - out(f->functionToken); - out(f->lparenToken); - accept(f->formals); - out(f->rparenToken); - out(f->lbraceToken); - if (scoped) - ++expressionDepth; - if (f->body) { - if (f->body->next || scoped) { - lnAcceptIndented(f->body); - lw.newline(); - } else { - baseIndent = lw.increaseIndent(1); - accept(f->body); - lw.decreaseIndent(1, baseIndent); - } - } - if (scoped) - --expressionDepth; - out(f->rbraceToken); - lw.newline(); - postVisit(property); + // 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(";"); } - lw.decreaseIndent(1, baseIndent); - out("}"); - postVisit(ast); - return false; } +} + +void ScriptFormatter::endVisit(AST::ExportClause *ast) +{ + if (ast->exportsList) { + lw.space(); + } + out(ast->rightBraceToken); +} - bool visit(ClassElementList *) override { return true; } - bool visit(Program *) override { return true; } - bool visit(NameSpaceImport *) override { return true; } - bool visit(ImportSpecifier *) override { return true; } - bool visit(ImportsList *) override { return true; } - bool visit(NamedImports *) override { return true; } - bool visit(FromClause *) override { return true; } - bool visit(ImportClause *) override { return true; } - bool visit(ImportDeclaration *) override { return true; } - bool visit(ExportSpecifier *) override { return true; } - bool visit(ExportsList *) override { return true; } - bool visit(ExportClause *) override { return true; } - bool visit(ExportDeclaration *) override { return true; } - bool visit(ESModule *) override { return true; } - bool visit(DebuggerStatement *) override { return true; } - bool visit(Type *) override { return true; } - bool visit(TypeArgument *) override { return true; } - bool visit(TypeAnnotation *) override { return true; } - - // overridden to use BasicVisitor (and ensure warnings about new added AST) - void endVisit(UiProgram *) override { } - void endVisit(UiImport *) override { } - void endVisit(UiHeaderItemList *) override { } - void endVisit(UiPragma *) override { } - void endVisit(UiPublicMember *) override { } - void endVisit(UiSourceElement *) override { } - void endVisit(UiObjectDefinition *) override { } - void endVisit(UiObjectInitializer *) override { } - void endVisit(UiObjectBinding *) override { } - void endVisit(UiScriptBinding *) override { } - void endVisit(UiArrayBinding *) override { } - void endVisit(UiParameterList *) override { } - void endVisit(UiObjectMemberList *) override { } - void endVisit(UiArrayMemberList *) override { } - void endVisit(UiQualifiedId *) override { } - void endVisit(UiEnumDeclaration *) override { } - void endVisit(UiEnumMemberList *) override { } - void endVisit(UiVersionSpecifier *) override { } - void endVisit(UiInlineComponent *) override { } - void endVisit(UiAnnotation *) override { } - void endVisit(UiAnnotationList *) override { } - void endVisit(UiRequired *) override { } - void endVisit(TypeExpression *) override { } - void endVisit(ThisExpression *) override { } - void endVisit(IdentifierExpression *) override { } - void endVisit(NullExpression *) override { } - void endVisit(TrueLiteral *) override { } - void endVisit(FalseLiteral *) override { } - void endVisit(SuperLiteral *) override { } - void endVisit(StringLiteral *) override { } - void endVisit(TemplateLiteral *) override { } - void endVisit(NumericLiteral *) override { } - void endVisit(RegExpLiteral *) override { } - void endVisit(ArrayPattern *) override { } - void endVisit(ObjectPattern *) override { } - void endVisit(PatternElementList *) override { } - void endVisit(PatternPropertyList *) override { } - void endVisit(PatternElement *) override { } - void endVisit(PatternProperty *) override { } - void endVisit(Elision *) override { } - void endVisit(NestedExpression *) override { } - void endVisit(IdentifierPropertyName *) override { } - void endVisit(StringLiteralPropertyName *) override { } - void endVisit(NumericLiteralPropertyName *) override { } - void endVisit(ComputedPropertyName *) override { out("]"); } - void endVisit(ArrayMemberExpression *) override { } - void endVisit(FieldMemberExpression *) override { } - void endVisit(TaggedTemplate *) override { } - void endVisit(NewMemberExpression *) override { } - void endVisit(NewExpression *) override { } - void endVisit(CallExpression *) override { } - void endVisit(ArgumentList *) override { } - void endVisit(PostIncrementExpression *) override { } - void endVisit(PostDecrementExpression *) override { } - void endVisit(DeleteExpression *) override { } - void endVisit(VoidExpression *) override { } - void endVisit(TypeOfExpression *) override { } - void endVisit(PreIncrementExpression *) override { } - void endVisit(PreDecrementExpression *) override { } - void endVisit(UnaryPlusExpression *) override { } - void endVisit(UnaryMinusExpression *) override { } - void endVisit(TildeExpression *) override { } - void endVisit(NotExpression *) override { } - void endVisit(BinaryExpression *) override { } - void endVisit(ConditionalExpression *) override { } - void endVisit(Expression *) override { } - void endVisit(Block *) override { } - void endVisit(StatementList *) override { } - void endVisit(VariableStatement *) override { } - void endVisit(VariableDeclarationList *) override { } - void endVisit(EmptyStatement *) override { } - void endVisit(ExpressionStatement *) override { } - void endVisit(IfStatement *) override { } - void endVisit(DoWhileStatement *) override { } - void endVisit(WhileStatement *) override { } - void endVisit(ForStatement *) override { } - void endVisit(ForEachStatement *) override { } - void endVisit(ContinueStatement *) override { } - void endVisit(BreakStatement *) override { } - void endVisit(ReturnStatement *) override { } - void endVisit(YieldExpression *) override { } - void endVisit(WithStatement *) override { } - void endVisit(SwitchStatement *) override { } - void endVisit(CaseBlock *) override { } - void endVisit(CaseClauses *) override { } - void endVisit(CaseClause *) override { } - void endVisit(DefaultClause *) override { } - void endVisit(LabelledStatement *) override { } - void endVisit(ThrowStatement *) override { } - void endVisit(TryStatement *) override { } - void endVisit(Catch *) override { } - void endVisit(Finally *) override { } - void endVisit(FunctionDeclaration *) override { } - void endVisit(FunctionExpression *) override { } - void endVisit(FormalParameterList *) override { } - void endVisit(ClassExpression *) override { } - void endVisit(ClassDeclaration *) override { } - void endVisit(ClassElementList *) override { } - void endVisit(Program *) override { } - void endVisit(NameSpaceImport *) override { } - void endVisit(ImportSpecifier *) override { } - void endVisit(ImportsList *) override { } - void endVisit(NamedImports *) override { } - void endVisit(FromClause *) override { } - void endVisit(ImportClause *) override { } - void endVisit(ImportDeclaration *) override { } - void endVisit(ExportSpecifier *) override { } - void endVisit(ExportsList *) override { } - void endVisit(ExportClause *) override { } - void endVisit(ExportDeclaration *) override { } - void endVisit(ESModule *) override { } - void endVisit(DebuggerStatement *) override { } - void endVisit(Type *) override { } - void endVisit(TypeArgument *) override { } - void endVisit(TypeAnnotation *) override { } - - void throwRecursionDepthError() override - { - out("/* ERROR: Hit recursion limit visiting AST, rewrite failed */"); +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, std::shared_ptr<AstComments> comments, - const std::function<QStringView(SourceLocation)> loc2Str, AST::Node *n) +void reformatAst(OutWriter &lw, const std::shared_ptr<AstComments> &comments, + const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n) { if (n) { - Rewriter rewriter(lw, comments, loc2Str, n); + ScriptFormatter formatter(lw, comments, loc2Str, n); } } diff --git a/src/qmldom/qqmldomreformatter_p.h b/src/qmldom/qqmldomreformatter_p.h index 4ff0d39ee0..48e2c63881 100644 --- a/src/qmldom/qqmldomreformatter_p.h +++ b/src/qmldom/qqmldomreformatter_p.h @@ -27,9 +27,193 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -QMLDOM_EXPORT void reformatAst(OutWriter &lw, std::shared_ptr<AstComments> comments, - const std::function<QStringView(SourceLocation)> loc2Str, - AST::Node *n); +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); + + 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::YieldExpression *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 diff --git a/src/qmldom/qqmldomscanner.cpp b/src/qmldom/qqmldomscanner.cpp index f73a1b1a5a..84cd591fb0 100644 --- a/src/qmldom/qqmldomscanner.cpp +++ b/src/qmldom/qqmldomscanner.cpp @@ -375,7 +375,7 @@ bool Token::lexKindIsInvalid(int kind) return false; } -void Token::dump(Sink s, QStringView line) const +void Token::dump(const Sink &s, QStringView line) const { s(u"{"); sinkInt(s, offset); diff --git a/src/qmldom/qqmldomscanner_p.h b/src/qmldom/qqmldomscanner_p.h index 45141033b4..5ff1c5b767 100644 --- a/src/qmldom/qqmldomscanner_p.h +++ b/src/qmldom/qqmldomscanner_p.h @@ -44,10 +44,10 @@ public: 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(Sink s, QStringView line = QStringView()) const; + void dump(const Sink &s, QStringView line = QStringView()) const; QString toString(QStringView line = QStringView()) const { - return dumperToString([line, this](Sink s) { this->dump(s, line); }); + return dumperToString([line, this](const Sink &s) { this->dump(s, line); }); } static int compare(const Token &t1, const Token &t2) diff --git a/src/qmldom/qqmldomscriptelements.cpp b/src/qmldom/qqmldomscriptelements.cpp new file mode 100644 index 0000000000..c0a48e0ce3 --- /dev/null +++ b/src/qmldom/qqmldomscriptelements.cpp @@ -0,0 +1,358 @@ +// 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); + } + for (auto it = m_values.begin(); it != m_values.end(); ++it) { + cont &= self.dvValueField(visitor, it->first, 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..98d863f14e --- /dev/null +++ b/src/qmldom/qqmldomscriptelements_p.h @@ -0,0 +1,423 @@ +// 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 "qqmldomelements_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)); + } + + ScriptElementVariant elementChild(const QQmlJS::Dom::FieldType &field) + { + auto it = m_children.find(field); + if (it == m_children.end()) + return {}; + if (!std::holds_alternative<ScriptElementVariant>(it->second)) + return {}; + return std::get<ScriptElementVariant>(it->second); + } + + void insertValue(QStringView name, const QCborValue &v) + { + m_values.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; + // value fields + std::map<QQmlJS::Dom::FieldType, QCborValue> m_values; + 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 081a6abf81..4859614f77 100644 --- a/src/qmldom/qqmldomstringdumper.cpp +++ b/src/qmldom/qqmldomstringdumper.cpp @@ -43,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); @@ -59,7 +59,7 @@ 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; @@ -95,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: @@ -117,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){ @@ -131,7 +131,7 @@ 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) { @@ -157,7 +157,7 @@ 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" "; @@ -173,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) @@ -186,7 +186,7 @@ void sinkNewline(Sink s, int indent) * \brief A sink that ignores whatever it receives */ -QDebug operator<<(QDebug d, Dumper dumper) +QDebug operator<<(QDebug d, const Dumper &dumper) { QDebug dd = d.noquote().nospace(); dumper([&dd](QStringView s) { dd << s; }); diff --git a/src/qmldom/qqmldomstringdumper_p.h b/src/qmldom/qqmldomstringdumper_p.h index 5f1a633809..cf5c8e6483 100644 --- a/src/qmldom/qqmldomstringdumper_p.h +++ b/src/qmldom/qqmldomstringdumper_p.h @@ -32,7 +32,7 @@ namespace Dom { using Sink = function_ref<void(QStringView)>; using SinkF = std::function<void(QStringView)>; -using DumperFunction = std::function<void(Sink)>; +using DumperFunction = std::function<void(const Sink &)>; class Dumper{ public: @@ -43,7 +43,7 @@ private: // would be the second user defined conversion. // 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; @@ -54,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)) {} @@ -63,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; @@ -96,24 +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(Sink s, int indent = 0); +QMLDOM_EXPORT void sinkNewline(const Sink &s, int indent = 0); -QMLDOM_EXPORT void dumpErrorLevel(Sink s, ErrorLevel level); +QMLDOM_EXPORT void dumpErrorLevel(const Sink &s, ErrorLevel level); -QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, QDebug debug); +QMLDOM_EXPORT void dumperToQDebug(const Dumper &dumper, QDebug debug); -QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, ErrorLevel level = ErrorLevel::Debug); +QMLDOM_EXPORT void dumperToQDebug(const Dumper &dumper, ErrorLevel level = ErrorLevel::Debug); -QMLDOM_EXPORT QDebug operator<<(QDebug d, Dumper dumper); +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 4e82a01933..cfc20d50ef 100644 --- a/src/qmldom/qqmldomtop.cpp +++ b/src/qmldom/qqmldomtop.cpp @@ -1,6 +1,7 @@ // 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" @@ -8,6 +9,7 @@ #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> @@ -53,21 +55,21 @@ using std::shared_ptr; if force is true the file is always read */ -Path DomTop::canonicalPath(DomItem &) const +Path DomTop::canonicalPath(const DomItem &) const { return canonicalPath(); } -DomItem DomTop::containingObject(DomItem &) const +DomItem DomTop::containingObject(const DomItem &) const { return DomItem(); } -bool DomTop::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool DomTop::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { static QHash<QString, QString> knownFields; static QBasicMutex m; - auto toField = [](QString f) mutable -> QStringView { + auto toField = [](const QString &f) mutable -> QStringView { QMutexLocker l(&m); if (!knownFields.contains(f)) knownFields[f] = f; @@ -119,11 +121,10 @@ 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(std::shared_ptr<DomUniverse> univ) +std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse( + const std::shared_ptr<DomUniverse> &univ) { const auto next = [] { Q_CONSTINIT static std::atomic<int> counter(0); @@ -136,9 +137,9 @@ std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(std::shared_ptr<DomU QLatin1String("universe") + QString::number(next())); } -DomItem DomUniverse::create(QString universeName, Options options) +DomItem DomUniverse::create(const QString &universeName) { - auto res = std::make_shared<DomUniverse>(universeName, options); + auto res = std::make_shared<DomUniverse>(universeName); return DomItem(res); } @@ -147,66 +148,51 @@ Path DomUniverse::canonicalPath() const return Path::Root(u"universe"); } -bool DomUniverse::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool DomUniverse::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && DomTop::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::name, name()); - cont = cont && self.dvValueField(visitor, Fields::options, int(options())); cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() { return self.subMapItem(Map( Path::Field(Fields::globalScopeWithName), - [this](DomItem &map, QString key) { return map.copy(globalScopeWithName(key)); }, - [this](DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope"))); + [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](DomItem &map, QString key) { return map.copy(qmlDirectoryWithPath(key)); }, - [this](DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory"))); + [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](DomItem &map, QString key) { return map.copy(qmldirFileWithPath(key)); }, - [this](DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile"))); + [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](DomItem &map, QString key) { return map.copy(qmlFileWithPath(key)); }, - [this](DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile"))); + [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](DomItem &map, QString key) { return map.copy(jsFileWithPath(key)); }, - [this](DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile"))); + [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](DomItem &map, QString key) { return map.copy(qmltypesFileWithPath(key)); }, - [this](DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile"))); - }); - cont = cont && self.dvItemField(visitor, Fields::queue, [this, &self]() { - QQueue<ParsingTask> q = queue(); - return self.subListItem(List( - Path::Field(Fields::queue), - [q](DomItem &list, index_type i) { - if (i >= 0 && i < q.size()) - return list.subDataItem(PathEls::Index(i), q.at(i).toCbor(), - ConstantData::Options::FirstMapIsFields); - else - return DomItem(); - }, - [q](DomItem &) { return index_type(q.size()); }, nullptr, - QLatin1String("ParsingTask"))); + [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(DomItem &) const +std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const { QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)"))); auto m = r.match(m_name); @@ -219,14 +205,7 @@ std::shared_ptr<OwningItem> DomUniverse::doCopy(DomItem &) const return res; } -void DomUniverse::loadFile(DomItem &self, QString filePath, QString logicalPath, Callback callback, - LoadOptions loadOptions, std::optional<DomType> fileType) -{ - loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - callback, loadOptions, fileType); -} - -static DomType fileTypeForPath(DomItem &self, QString canonicalFilePath) +static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath) { if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive) || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive)) { @@ -236,12 +215,16 @@ static DomType fileTypeForPath(DomItem &self, QString canonicalFilePath) } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(), Qt::CaseInsensitive) == 0) { - return DomType::QmltypesFile; + return DomType::QmldirFile; } else if (QFileInfo(canonicalFilePath).isDir()) { return DomType::QmlDirectory; - } else { + } 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::filteTypeForPath", + .error(QCoreApplication::translate("Dom::fileTypeForPath", "Could not detect type of file %1") .arg(canonicalFilePath)) .handle()); @@ -249,245 +232,120 @@ static DomType fileTypeForPath(DomItem &self, QString canonicalFilePath) return DomType::Empty; } -void DomUniverse::loadFile(DomItem &self, QString canonicalFilePath, QString logicalPath, - QString code, QDateTime codeDate, Callback callback, - LoadOptions loadOptions, std::optional<DomType> fileType) +DomUniverse::LoadResult DomUniverse::loadFile(const FileToLoad &file, DomType fileType, + DomCreationOptions creationOptions) { - DomType fType = (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath)); - switch (fType) { + DomItem univ(shared_from_this()); + switch (fileType) { case DomType::QmlFile: case DomType::QmltypesFile: case DomType::QmldirFile: - case DomType::QmlDirectory: { - // Protect the queue from concurrent access. - QMutexLocker l(mutex()); - m_queue.enqueue(ParsingTask{ QDateTime::currentDateTimeUtc(), loadOptions, fType, - canonicalFilePath, logicalPath, code, codeDate, - self.ownerAs<DomUniverse>(), callback }); - break; + 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: - self.addError(myErrors() + univ.addError(myErrors() .error(tr("Ignoring request to load file %1 of unexpected type %2, " "calling callback immediately") - .arg(canonicalFilePath, domTypeToString(fType))) + .arg(file.canonicalPath(), domTypeToString(fileType))) .handle()); Q_ASSERT(false && "loading non supported file type"); - callback(Path(), DomItem::empty, DomItem::empty); - return; + return {}; } - if (m_options & Option::SingleThreaded) - execQueue(); // immediate execution in the same thread } -template<typename T> -QPair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>> -updateEntry(DomItem &univ, std::shared_ptr<T> newItem, - QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex) -{ - std::shared_ptr<ExternalItemPair<T>> oldValue; - std::shared_ptr<ExternalItemPair<T>> newValue; - QString canonicalPath = newItem->canonicalFilePath(); - QDateTime now = QDateTime::currentDateTimeUtc(); - { - 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; - } else { - DomItem oldValueObj = univ.copy(oldValue); - newValue = oldValue->makeCopy(oldValueObj); - newValue->current = newItem; - newValue->currentExposedAt = now; - if (newItem->isValid()) { - newValue->valid = newItem; - newValue->validExposedAt = now; - } - it = map.insert(it, canonicalPath, newValue); - } - } else { - newValue = std::make_shared<ExternalItemPair<T>>( - (newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now); - map.insert(canonicalPath, newValue); - } +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 qMakePair(oldValue, newValue); + return { std::move(oldValue), std::move(newValue) }; } -void DomUniverse::execQueue() +/*! + \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 { - ParsingTask t; - { - // Protect the queue from concurrent access. - QMutexLocker l(mutex()); - if (m_queue.isEmpty()) - return; - 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(); - } - QString canonicalPath = t.canonicalPath; - QString code = t.contents; - QDateTime contentDate = t.contentsDate; - bool skipParse = false; - DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue) - DomItem newValue; // current ExternalItemPair - DomItem univ = DomItem(topPtr); - QFileInfo path(canonicalPath); - QVector<ErrorMessage> messages; - - if (t.kind == DomType::QmlFile || t.kind == DomType::QmltypesFile - || t.kind == DomType::QmldirFile || t.kind == DomType::QmlDirectory) { - auto getValue = [&t, this, &canonicalPath]() -> std::shared_ptr<ExternalItemPairBase> { - if (t.kind == DomType::QmlFile) - return m_qmlFileWithPath.value(canonicalPath); - else if (t.kind == DomType::QmltypesFile) - return m_qmlFileWithPath.value(canonicalPath); - else if (t.kind == DomType::QmldirFile) - return m_qmlFileWithPath.value(canonicalPath); - else if (t.kind == DomType::QmlDirectory) - return m_qmlDirectoryWithPath.value(canonicalPath); - else - Q_ASSERT(false); - return {}; - }; - if (code.isEmpty()) { - QFile file(canonicalPath); - canonicalPath = path.canonicalFilePath(); - if (canonicalPath.isEmpty()) { - messages.append(myErrors().error(tr("Non existing path %1").arg(t.canonicalPath))); - canonicalPath = t.canonicalPath; - } - { - QMutexLocker l(mutex()); - auto value = getValue(); - if (!(t.loadOptions & LoadOption::ForceLoad) && value) { - if (value && value->currentItem() - && path.lastModified() < value->currentItem()->lastDataUpdateAt()) { - oldValue = newValue = univ.copy(value); - skipParse = true; - } - } - } - if (!skipParse) { - contentDate = QDateTime::currentDateTimeUtc(); - if (QFileInfo(canonicalPath).isDir()) { - code = QDir(canonicalPath) - .entryList(QDir::NoDotAndDotDot | QDir::Files, QDir::Name) - .join(QLatin1Char('\n')); - } else if (!file.open(QIODevice::ReadOnly)) { - code = QStringLiteral(u""); - messages.append(myErrors().error(tr("Error opening path %1: %2 %3") - .arg(canonicalPath, - QString::number(file.error()), - file.errorString()))); - } else { - code = QString::fromUtf8(file.readAll()); - file.close(); - } - } - } - if (!skipParse) { - QMutexLocker l(mutex()); - if (auto value = getValue()) { - QString oldCode = value->currentItem()->code(); - if (value && value->currentItem() && !oldCode.isNull() && oldCode == code) { - skipParse = true; - newValue = oldValue = univ.copy(value); - if (value->currentItem()->lastDataUpdateAt() < contentDate) - value->currentItem()->refreshedDataAt(contentDate); - } - } - } - if (!skipParse) { - QDateTime now(QDateTime::currentDateTimeUtc()); - if (t.kind == DomType::QmlFile) { - auto qmlFile = std::make_shared<QmlFile>(canonicalPath, code, contentDate); - auto envPtr = std::make_shared<DomEnvironment>( - QStringList(), DomEnvironment::Option::NoDependencies, topPtr); - envPtr->addQmlFile(qmlFile); - DomItem env(envPtr); - if (qmlFile->isValid()) { - MutableDomItem qmlFileObj(env.copy(qmlFile)); - createDom(qmlFileObj); - } else { - QString errs; - DomItem qmlFileObj = env.copy(qmlFile); - qmlFile->iterateErrors(qmlFileObj, [&errs](DomItem, ErrorMessage m) { - errs += m.toString(); - errs += u"\n"; - return true; - }); - qCWarning(domLog).noquote().nospace() - << "Parsed invalid file " << canonicalPath << errs; - } - auto change = updateEntry<QmlFile>(univ, qmlFile, m_qmlFileWithPath, mutex()); - oldValue = univ.copy(change.first); - newValue = univ.copy(change.second); - } else if (t.kind == DomType::QmltypesFile) { - auto qmltypesFile = std::make_shared<QmltypesFile>( - canonicalPath, code, contentDate); - QmltypesReader reader(univ.copy(qmltypesFile)); - reader.parse(); - auto change = updateEntry<QmltypesFile>(univ, qmltypesFile, m_qmltypesFileWithPath, - mutex()); - oldValue = univ.copy(change.first); - newValue = univ.copy(change.second); - } else if (t.kind == DomType::QmldirFile) { - shared_ptr<QmldirFile> qmldirFile = - QmldirFile::fromPathAndCode(canonicalPath, code); - auto change = - updateEntry<QmldirFile>(univ, qmldirFile, m_qmldirFileWithPath, mutex()); - oldValue = univ.copy(change.first); - newValue = univ.copy(change.second); - } else if (t.kind == DomType::QmlDirectory) { - auto qmlDirectory = std::make_shared<QmlDirectory>( - canonicalPath, code.split(QLatin1Char('\n')), contentDate); - auto change = updateEntry<QmlDirectory>(univ, qmlDirectory, m_qmlDirectoryWithPath, - mutex()); - oldValue = univ.copy(change.first); - newValue = univ.copy(change.second); - } else { - Q_ASSERT(false); - } + 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() }; } - for (const ErrorMessage &m : messages) - newValue.addError(m); - // to do: tell observers? - // execute callback - if (t.callback) { - Path p; - if (t.kind == DomType::QmlFile) - p = Paths::qmlFileInfoPath(canonicalPath); - else if (t.kind == DomType::QmltypesFile) - p = Paths::qmltypesFileInfoPath(canonicalPath); - else if (t.kind == DomType::QmldirFile) - p = Paths::qmldirFileInfoPath(canonicalPath); - else if (t.kind == DomType::QmlDirectory) - p = Paths::qmlDirectoryInfoPath(canonicalPath); - else - Q_ASSERT(false); - t.callback(p, oldValue, newValue); + // 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); } - } else { - Q_ASSERT(false && "Unhandled kind in queue"); } + + // 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()); - auto toDelete = [path](auto it) { + const auto toDelete = [path](const auto &it) { QString p = it.key(); return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/'); }; @@ -498,7 +356,191 @@ void DomUniverse::removePath(const QString &path) m_qmltypesFileWithPath.removeIf(toDelete); } -std::shared_ptr<OwningItem> LoadInfo::doCopy(DomItem &self) const +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) { @@ -506,7 +548,7 @@ std::shared_ptr<OwningItem> LoadInfo::doCopy(DomItem &self) const 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](Sink sink) { + .warning([&self](const Sink &sink) { sink(u"Copying an in progress LoadInfo, which is most likely an error ("); self.dump(sink); sink(u")"); @@ -521,12 +563,12 @@ std::shared_ptr<OwningItem> LoadInfo::doCopy(DomItem &self) const return res; } -Path LoadInfo::canonicalPath(DomItem &) const +Path LoadInfo::canonicalPath(const DomItem &) const { return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(elementCanonicalPath().toString()); } -bool LoadInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool LoadInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = OwningItem::iterateDirectSubpaths(self, visitor); cont = cont && self.dvValueField(visitor, Fields::status, int(status())); @@ -539,8 +581,8 @@ bool LoadInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -void LoadInfo::addEndCallback(DomItem &self, - std::function<void(Path, DomItem &, DomItem &)> callback) +void LoadInfo::addEndCallback(const DomItem &self, + std::function<void(Path, const DomItem &, const DomItem &)> callback) { if (!callback) return; @@ -562,7 +604,7 @@ void LoadInfo::addEndCallback(DomItem &self, callback(p, el, el); } -void LoadInfo::advanceLoad(DomItem &self) +void LoadInfo::advanceLoad(const DomItem &self) { Status myStatus; Dependency dep; @@ -607,26 +649,27 @@ void LoadInfo::advanceLoad(DomItem &self) case Status::InProgress: if (depValid) { refreshedDataAt(QDateTime::currentDateTimeUtc()); + auto envPtr = self.environment().ownerAs<DomEnvironment>(); + Q_ASSERT(envPtr && "missing environment"); if (!dep.uri.isEmpty()) { - self.loadModuleDependency( + envPtr->loadModuleDependency( dep.uri, dep.version, - [this, self, dep](Path, DomItem &, DomItem &) mutable { - finishedLoadingDep(self, dep); + [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()) { - DomItem env = self.environment(); - if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) - envPtr->loadFile( - env, dep.filePath, QString(), - [this, self, dep](Path, DomItem &, DomItem &) mutable { - finishedLoadingDep(self, dep); - }, - nullptr, nullptr, LoadOption::DefaultLoad, dep.fileType, - self.errorHandler()); - else - Q_ASSERT(false && "missing environment"); + 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 { Q_ASSERT(false && "dependency without uri and filePath"); } @@ -643,7 +686,7 @@ void LoadInfo::advanceLoad(DomItem &self) } } -void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d) +void LoadInfo::finishedLoadingDep(const DomItem &self, const Dependency &d) { bool didRemove = false; bool unexpectedState = false; @@ -668,7 +711,7 @@ void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d) } } if (!didRemove) { - addErrorLocal(DomEnvironment::myErrors().error([&self](Sink sink) { + addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) { sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress " u"()"); self.dump(sink); @@ -678,7 +721,7 @@ void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d) && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress"); } if (unexpectedState) { - addErrorLocal(DomEnvironment::myErrors().error([&self](Sink sink) { + addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) { sink(u"LoadInfo::finishedLoadingDep found an unexpected state ("); self.dump(sink); sink(u")"); @@ -689,9 +732,9 @@ void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d) execEnd(self); } -void LoadInfo::execEnd(DomItem &self) +void LoadInfo::execEnd(const DomItem &self) { - QList<std::function<void(Path, DomItem &, DomItem &)>> endCallbacks; + QList<std::function<void(Path, const DomItem &, const DomItem &)>> endCallbacks; bool unexpectedState = false; { QMutexLocker l(mutex()); @@ -704,7 +747,7 @@ void LoadInfo::execEnd(DomItem &self) DomItem el = self.path(p); { auto cleanup = qScopeGuard([this, p, &el] { - QList<std::function<void(Path, DomItem &, DomItem &)>> otherCallbacks; + QList<std::function<void(Path, const DomItem &, const DomItem &)>> otherCallbacks; bool unexpectedState2 = false; { QMutexLocker l(mutex()); @@ -726,7 +769,7 @@ void LoadInfo::execEnd(DomItem &self) } } -void LoadInfo::doAddDependencies(DomItem &self) +void LoadInfo::doAddDependencies(const DomItem &self) { if (!elementCanonicalPath()) { DomEnvironment::myErrors() @@ -739,55 +782,36 @@ void LoadInfo::doAddDependencies(DomItem &self) DomItem el = self.path(elementCanonicalPath()); if (el.internalKind() == DomType::ExternalItemInfo) { DomItem currentFile = el.field(Fields::currentItem); - DomItem currentImports = currentFile.field(Fields::imports); QString currentFilePath = currentFile.canonicalFilePath(); - int iEnd = currentImports.indexes(); - for (int i = 0; i < iEnd; ++i) { - DomItem import = currentImports.index(i); - if (const Import *importPtr = import.as<Import>()) { - if (importPtr->uri.isDirectory()) { - QString path = importPtr->uri.absoluteLocalPath(currentFilePath); - if (!path.isEmpty()) { - addDependency(self, - Dependency { QString(), importPtr->version, path, - DomType::QmlDirectory }); - } else { - self.addError(DomEnvironment::myErrors().error( - tr("Ignoring dependencies for non resolved path import %1") - .arg(importPtr->uri.toString()))); - } - } else { - addDependency(self, - Dependency { importPtr->uri.moduleUri(), importPtr->version, - QString(), DomType::ModuleIndex }); - } - } - } - 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](QString, DomItem &els) { - return els.visitIndexes([this, &self](DomItem &el) { - if (const Reference *ref = el.as<Reference>()) { + // 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::QmlFile }); + addDependency( + self, + Dependency{ QString(), Version(), canonicalPath.headName(), + DomType::QmltypesFile }); } - return true; + } + 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) { @@ -798,7 +822,7 @@ void LoadInfo::doAddDependencies(DomItem &self) DomType::QmldirFile }); } QString uri = elPtr->uri(); - addEndCallback(self, [uri, qmldirs](Path, DomItem &, DomItem &newV) { + 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>()) { @@ -818,7 +842,7 @@ void LoadInfo::doAddDependencies(DomItem &self) } } -void LoadInfo::addDependency(DomItem &self, const Dependency &dep) +void LoadInfo::addDependency(const DomItem &self, const Dependency &dep) { bool unexpectedState = false; { @@ -835,105 +859,14 @@ void LoadInfo::addDependency(DomItem &self, const Dependency &dep) \class QQmlJS::Dom::DomEnvironment \brief Represents a consistent set of types organized in modules, it is the top level of the DOM - */ -template<typename T> -DomTop::Callback envCallbackForFile( - DomItem &self, QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> DomEnvironment::*map, - std::shared_ptr<ExternalItemInfo<T>> (DomEnvironment::*lookupF)(DomItem &, QString, - EnvLookup) const, - DomTop::Callback loadCallback, DomTop::Callback allDirectDepsCallback, - DomTop::Callback endCallback) -{ - std::shared_ptr<DomEnvironment> ePtr = self.ownerAs<DomEnvironment>(); - std::weak_ptr<DomEnvironment> selfPtr = ePtr; - std::shared_ptr<DomEnvironment> basePtr = ePtr->base(); - return [selfPtr, basePtr, map, lookupF, loadCallback, allDirectDepsCallback, - endCallback](Path, DomItem &, DomItem &newItem) { - shared_ptr<DomEnvironment> envPtr = selfPtr.lock(); - if (!envPtr) - return; - DomItem env = DomItem(envPtr); - shared_ptr<ExternalItemInfo<T>> oldValue; - shared_ptr<ExternalItemInfo<T>> newValue; - shared_ptr<T> newItemPtr; - if (envPtr->options() & DomEnvironment::Option::KeepValid) - newItemPtr = newItem.field(Fields::validItem).ownerAs<T>(); - if (!newItemPtr) - newItemPtr = newItem.field(Fields::currentItem).ownerAs<T>(); - Q_ASSERT(newItemPtr && "callbackForQmlFile reached without current qmlFile"); - { - QMutexLocker l(envPtr->mutex()); - oldValue = ((*envPtr).*map).value(newItem.canonicalFilePath()); - } - if (oldValue) { - // we do not change locally loaded files (avoid loading a file more than once) - newValue = oldValue; - } else { - if (basePtr) { - DomItem baseObj(basePtr); - oldValue = ((*basePtr).*lookupF)(baseObj, newItem.canonicalFilePath(), - EnvLookup::BaseOnly); - } - if (oldValue) { - DomItem oldValueObj = env.copy(oldValue); - newValue = oldValue->makeCopy(oldValueObj); - if (newValue->current != newItemPtr) { - newValue->current = newItemPtr; - newValue->setCurrentExposedAt(QDateTime::currentDateTimeUtc()); - } - } else { - newValue = std::make_shared<ExternalItemInfo<T>>( - newItemPtr, QDateTime::currentDateTimeUtc()); - } - { - QMutexLocker l(envPtr->mutex()); - auto value = ((*envPtr).*map).value(newItem.canonicalFilePath()); - if (value) { - oldValue = newValue = value; - } else { - ((*envPtr).*map).insert(newItem.canonicalFilePath(), newValue); - } - } - } - Path p = env.copy(newValue).canonicalPath(); - { - auto depLoad = qScopeGuard([p, &env, envPtr, allDirectDepsCallback, endCallback] { - if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) { - auto loadInfo = std::make_shared<LoadInfo>(p); - if (!p) - Q_ASSERT(false); - DomItem loadInfoObj = env.copy(loadInfo); - loadInfo->addEndCallback(loadInfoObj, allDirectDepsCallback); - envPtr->addLoadInfo(env, loadInfo); - } - if (endCallback) - envPtr->addAllLoadedCallback(env, - [p, endCallback](Path, DomItem &, DomItem &env) { - DomItem el = env.path(p); - endCallback(p, el, el); - }); - }); - if (loadCallback) { - DomItem oldValueObj = env.copy(oldValue); - DomItem newValueObj = env.copy(newValue); - loadCallback(p, oldValueObj, newValueObj); - } - if ((envPtr->options() & DomEnvironment::Option::NoDependencies) - && allDirectDepsCallback) { - DomItem oldValueObj = env.copy(oldValue); - DomItem newValueObj = env.copy(newValue); - env.addError(DomEnvironment::myErrors().warning( - QLatin1String("calling allDirectDepsCallback immediately for load with " - "NoDependencies of %1") - .arg(newItem.canonicalFilePath()))); - allDirectDepsCallback(p, oldValueObj, newValueObj); - } - } - }; -} +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; } @@ -948,7 +881,7 @@ Path DomEnvironment::canonicalPath() const return Path::Root(u"env"); } -bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool DomEnvironment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && DomTop::iterateDirectSubpaths(self, visitor); @@ -962,54 +895,54 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() { return self.subMapItem(Map( Path::Field(Fields::globalScopeWithName), - [&self, this](DomItem &map, QString key) { + [&self, this](const DomItem &map, const QString &key) { return map.copy(globalScopeWithName(self, key)); }, - [&self, this](DomItem &) { return globalScopeNames(self); }, + [&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](DomItem &map, QString key) { + [&self, this](const DomItem &map, const QString &key) { return map.copy(qmlDirectoryWithPath(self, key)); }, - [&self, this](DomItem &) { return qmlDirectoryPaths(self); }, + [&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](DomItem &map, QString key) { + [&self, this](const DomItem &map, const QString &key) { return map.copy(qmldirFileWithPath(self, key)); }, - [&self, this](DomItem &) { return qmldirFilePaths(self); }, + [&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](DomItem &map, QString key) { + [&self, this](const DomItem &map, const QString &key) { return map.copy(qmlDirWithPath(self, key)); }, - [&self, this](DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir"))); + [&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](DomItem &map, QString key) { + [&self, this](const DomItem &map, const QString &key) { return map.copy(qmlFileWithPath(self, key)); }, - [&self, this](DomItem &) { return qmlFilePaths(self); }, QLatin1String("QmlFile"))); + [&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](DomItem &map, QString key) { + [this](const DomItem &map, const QString &key) { DomItem mapOw(map.owner()); return map.copy(jsFileWithPath(mapOw, key)); }, - [this](DomItem &map) { + [this](const DomItem &map) { DomItem mapOw = map.owner(); return jsFilePaths(mapOw); }, @@ -1018,11 +951,11 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::qmltypesFileWithPath, [this, &self]() { return self.subMapItem(Map( Path::Field(Fields::qmltypesFileWithPath), - [this](DomItem &map, QString key) { + [this](const DomItem &map, const QString &key) { DomItem mapOw = map.owner(); return map.copy(qmltypesFileWithPath(mapOw, key)); }, - [this](DomItem &map) { + [this](const DomItem &map) { DomItem mapOw = map.owner(); return qmltypesFilePaths(mapOw); }, @@ -1031,10 +964,10 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) cont = cont && self.dvItemField(visitor, Fields::moduleIndexWithUri, [this, &self]() { return self.subMapItem(Map( Path::Field(Fields::moduleIndexWithUri), - [this](DomItem &map, QString key) { + [this](const DomItem &map, const QString &key) { return map.subMapItem(Map( map.pathFromOwner().key(key), - [this, key](DomItem &submap, QString subKey) { + [this, key](const DomItem &submap, const QString &subKey) { bool ok; int i = subKey.toInt(&ok); if (!ok) { @@ -1050,7 +983,7 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) moduleIndexWithUri(subMapOw, key, i); return submap.copy(mIndex); }, - [this, key](DomItem &subMap) { + [this, key](const DomItem &subMap) { QSet<QString> res; DomItem subMapOw = subMap.owner(); for (int mVersion : @@ -1065,7 +998,7 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) }, QLatin1String("ModuleIndex"))); }, - [this](DomItem &map) { + [this](const DomItem &map) { DomItem mapOw = map.owner(); return moduleIndexUris(mapOw); }, @@ -1090,14 +1023,14 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) ensureInfo(); return self.subListItem(List( Path::Field(Fields::loadsWithWork), - [loadsWithWork](DomItem &list, index_type i) { + [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](DomItem &) { + [loadsWithWork](const DomItem &) { return index_type(loadsWithWork.size()); }, nullptr, QLatin1String("Path"))); @@ -1107,22 +1040,22 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) ensureInfo(); return self.subListItem(List( Path::Field(Fields::inProgress), - [inProgress](DomItem &list, index_type i) { + [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](DomItem &) { return index_type(inProgress.size()); }, + [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](DomItem &map, QString pStr) { + [this](const DomItem &map, const QString &pStr) { bool hasErrors = false; - Path p = Path::fromString(pStr, [&hasErrors](ErrorMessage m) { + Path p = Path::fromString(pStr, [&hasErrors](const ErrorMessage &m) { switch (m.level) { case ErrorLevel::Debug: case ErrorLevel::Info: @@ -1138,7 +1071,7 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return map.copy(loadInfo(p)); return DomItem(); }, - [this](DomItem &) { + [this](const DomItem &) { QSet<QString> res; const auto infoPaths = loadInfoPaths(); for (const Path &p : infoPaths) @@ -1157,189 +1090,136 @@ bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) return cont; } -DomItem DomEnvironment::field(DomItem &self, QStringView name) const +DomItem DomEnvironment::field(const DomItem &self, QStringView name) const { return DomTop::field(self, name); } -std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(DomItem &self) const +std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(const DomItem &self) const { return std::static_pointer_cast<DomEnvironment>(doCopy(self)); } -void DomEnvironment::loadFile(DomItem &self, QString filePath, QString logicalPath, - DomTop::Callback loadCallback, DomTop::Callback directDepsCallback, - DomTop::Callback endCallback, LoadOptions loadOptions, - std::optional<DomType> fileType, ErrorHandler h) -{ - loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - loadCallback, directDepsCallback, endCallback, loadOptions, fileType, h); -} - -std::shared_ptr<OwningItem> DomEnvironment::doCopy(DomItem &) const +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_loadPaths, m_options, m_universe); + res = std::make_shared<DomEnvironment>(m_loadPaths, m_options, m_domCreationOptions, + m_universe); return res; } -void DomEnvironment::loadFile(DomItem &self, QString filePath, QString logicalPath, QString code, - QDateTime codeDate, Callback loadCallback, - Callback directDepsCallback, Callback endCallback, - LoadOptions loadOptions, std::optional<DomType> fileType, - ErrorHandler h) +void DomEnvironment::loadFile(const FileToLoad &file, const Callback &callback, + std::optional<DomType> fileType, const ErrorHandler &h) { - QFileInfo fileInfo(filePath); - QString canonicalFilePath = fileInfo.canonicalFilePath(); - if (canonicalFilePath.isEmpty()) { - if (code.isNull()) { - myErrors().error(tr("Non existing path to load: '%1'").arg(filePath)).handle(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 (directDepsCallback) - directDepsCallback(Path(), DomItem::empty, DomItem::empty); if (endCallback) - addAllLoadedCallback(self, [endCallback](Path, DomItem &, DomItem &) { + addAllLoadedCallback(self, [endCallback](Path, const DomItem &, const DomItem &) { endCallback(Path(), DomItem::empty, DomItem::empty); }); return; } else { - canonicalFilePath = filePath; + // fallback: path invalid but file's content is already available. + file.canonicalPath() = file.logicalPath(); } } + shared_ptr<ExternalItemInfoBase> oldValue, newValue; - DomType fType = (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath)); + const DomType fType = + (bool(fileType) ? (*fileType) : fileTypeForPath(self, file.canonicalPath())); switch (fType) { case DomType::QmlDirectory: { - { - QMutexLocker l(mutex()); - auto it = m_qmlDirectoryWithPath.find(canonicalFilePath); - if (it != m_qmlDirectoryWithPath.end()) - oldValue = newValue = *it; - } - if (!newValue && (options() & Option::NoReload) && m_base) { - if (auto v = m_base->qmlDirectoryWithPath(self, canonicalFilePath, EnvLookup::Normal)) { - oldValue = v; - QDateTime now = QDateTime::currentDateTimeUtc(); - auto newV = std::make_shared<ExternalItemInfo<QmlDirectory>>( - v->current, now, v->revision(), v->lastDataUpdateAt()); - newValue = newV; - QMutexLocker l(mutex()); - auto it = m_qmlDirectoryWithPath.find(canonicalFilePath); - if (it != m_qmlDirectoryWithPath.end()) - oldValue = newValue = *it; - else - m_qmlDirectoryWithPath.insert(canonicalFilePath, newV); - } - } + const auto &fetchResult = fetchFileFromEnvs<QmlDirectory>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; if (!newValue) { - self.universe().loadFile( - canonicalFilePath, logicalPath, code, codeDate, - callbackForQmlDirectory(self, loadCallback, directDepsCallback, endCallback), - loadOptions, fType); + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmlDirectory>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); return; } } break; case DomType::QmlFile: { - { - QMutexLocker l(mutex()); - auto it = m_qmlFileWithPath.find(canonicalFilePath); - if (it != m_qmlFileWithPath.end()) - oldValue = newValue = *it; - } - if (!newValue && (options() & Option::NoReload) && m_base) { - if (auto v = m_base->qmlFileWithPath(self, canonicalFilePath, EnvLookup::Normal)) { - oldValue = v; - QDateTime now = QDateTime::currentDateTimeUtc(); - auto newV = std::make_shared<ExternalItemInfo<QmlFile>>( - v->current, now, v->revision(), v->lastDataUpdateAt()); - newValue = newV; - QMutexLocker l(mutex()); - auto it = m_qmlFileWithPath.find(canonicalFilePath); - if (it != m_qmlFileWithPath.end()) - oldValue = newValue = *it; - else - m_qmlFileWithPath.insert(canonicalFilePath, newV); - } - } + const auto &fetchResult = fetchFileFromEnvs<QmlFile>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; if (!newValue) { - self.universe().loadFile( - canonicalFilePath, logicalPath, code, codeDate, - callbackForQmlFile(self, loadCallback, directDepsCallback, endCallback), - loadOptions, fType); + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmlFile>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); return; } } break; case DomType::QmltypesFile: { - { - QMutexLocker l(mutex()); - auto it = m_qmltypesFileWithPath.find(canonicalFilePath); - if (it != m_qmltypesFileWithPath.end()) - oldValue = newValue = *it; - } - if (!newValue && (options() & Option::NoReload) && m_base) { - if (auto v = m_base->qmltypesFileWithPath(self, canonicalFilePath, EnvLookup::Normal)) { - oldValue = v; - QDateTime now = QDateTime::currentDateTimeUtc(); - auto newV = std::make_shared<ExternalItemInfo<QmltypesFile>>( - v->current, now, v->revision(), v->lastDataUpdateAt()); - newValue = newV; - QMutexLocker l(mutex()); - auto it = m_qmltypesFileWithPath.find(canonicalFilePath); - if (it != m_qmltypesFileWithPath.end()) - oldValue = newValue = *it; - else - m_qmltypesFileWithPath.insert(canonicalFilePath, newV); - } - } + const auto &fetchResult = fetchFileFromEnvs<QmltypesFile>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; if (!newValue) { - self.universe().loadFile( - canonicalFilePath, logicalPath, code, codeDate, - callbackForQmltypesFile(self, loadCallback, directDepsCallback, endCallback), - loadOptions, fType); + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmltypesFile>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); return; } } break; case DomType::QmldirFile: { - { - QMutexLocker l(mutex()); - auto it = m_qmldirFileWithPath.find(canonicalFilePath); - if (it != m_qmldirFileWithPath.end()) - oldValue = newValue = *it; - } - if (!newValue && (options() & Option::NoReload) && m_base) { - if (auto v = m_base->qmldirFileWithPath(self, canonicalFilePath, EnvLookup::Normal)) { - oldValue = v; - QDateTime now = QDateTime::currentDateTimeUtc(); - auto newV = std::make_shared<ExternalItemInfo<QmldirFile>>( - v->current, now, v->revision(), v->lastDataUpdateAt()); - newValue = newV; - QMutexLocker l(mutex()); - auto it = m_qmldirFileWithPath.find(canonicalFilePath); - if (it != m_qmldirFileWithPath.end()) - oldValue = newValue = *it; - else - m_qmldirFileWithPath.insert(canonicalFilePath, newV); - } - } + const auto &fetchResult = fetchFileFromEnvs<QmldirFile>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; if (!newValue) { - self.universe().loadFile( - canonicalFilePath, logicalPath, code, codeDate, - callbackForQmldirFile(self, loadCallback, directDepsCallback, endCallback), - loadOptions, fType); + 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(filePath)).handle(h); + myErrors().error(tr("Unexpected file to load: '%1'").arg(file.canonicalPath())).handle(h); if (loadCallback) loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); - if (directDepsCallback) - directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); if (endCallback) endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); return; @@ -1353,27 +1233,34 @@ void DomEnvironment::loadFile(DomItem &self, QString filePath, QString logicalPa DomItem newValueObj = self.copy(newValue); loadCallback(p, oldValueObj, newValueObj); } - if (directDepsCallback) { - DomItem lInfoObj = self.copy(lInfo); - lInfo->addEndCallback(lInfoObj, directDepsCallback); - } } else { self.addError(myErrors().error(tr("missing load info in "))); if (loadCallback) loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); - if (directDepsCallback) - directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); } if (endCallback) - addAllLoadedCallback(self, [p, endCallback](Path, DomItem &, DomItem &env) { + 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(DomItem &self, QString uri, Version v, +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, - ErrorHandler errorHandler) + const ErrorHandler &errorHandler) { Q_ASSERT(!uri.contains(u'/')); Path p = Paths::moduleIndexPath(uri, v.majorVersion); @@ -1388,6 +1275,9 @@ void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v, 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); @@ -1399,25 +1289,39 @@ void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v, if (majorV > maxV) { QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow + QStringLiteral(u"/qmldir")); - if (fInfo.isFile()) + 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()) + if (fInfo.isFile()) { + qCDebug(QQmlJSDomImporting) + << "Found qmldir in " << fInfo.canonicalFilePath(); commonV = true; + } } } } - QAtomicInt toLoad((commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0)); - auto loadCallback2 = (loadCallback ? [p, loadCallback, toLoad](Path, DomItem &, DomItem &elV) mutable { - if (--toLoad == 0) { - DomItem el = elV.path(p); - loadCallback(p, el, el); - } - }: Callback()); + + // 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) @@ -1425,10 +1329,15 @@ void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v, loadCallback2, nullptr); else if (maxV < 0) { if (uri != u"QML") { - addErrorLocal(myErrors() - .warning(tr("Failed to find main qmldir file for %1 %2") - .arg(uri, v.stringValue())) - .handle()); + 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); @@ -1447,14 +1356,16 @@ void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v, loadCallback(p, DomItem::empty, DomItem::empty); } } - if (endCallback) - addAllLoadedCallback(self, [p, endCallback](Path, DomItem &, DomItem &env) { + 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(DomItem &self, Callback callback, ErrorHandler h) +void DomEnvironment::loadBuiltins(const Callback &callback, const ErrorHandler &h) { QString builtinsName = QLatin1String("builtins.qmltypes"); const auto lPaths = loadPaths(); @@ -1462,7 +1373,8 @@ void DomEnvironment::loadBuiltins(DomItem &self, Callback callback, ErrorHandler QDir dir(path); QFileInfo fInfo(dir.filePath(builtinsName)); if (fInfo.isFile()) { - self.loadFile(fInfo.canonicalFilePath(), QString(), callback, LoadOption::DefaultLoad); + loadFile(FileToLoad::fromFileSystem(shared_from_this(), fInfo.canonicalFilePath()), + callback); return; } } @@ -1517,7 +1429,7 @@ QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase, return res; } -QSet<QString> DomEnvironment::moduleIndexUris(DomItem &, EnvLookup lookup) const +QSet<QString> DomEnvironment::moduleIndexUris(const DomItem &, EnvLookup lookup) const { DomItem baseObj = DomItem(m_base); return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>( @@ -1525,7 +1437,7 @@ QSet<QString> DomEnvironment::moduleIndexUris(DomItem &, EnvLookup lookup) const m_moduleIndexWithUri, lookup); } -QSet<int> DomEnvironment::moduleIndexMajorVersions(DomItem &, QString uri, EnvLookup lookup) const +QSet<int> DomEnvironment::moduleIndexMajorVersions(const DomItem &, const QString &uri, EnvLookup lookup) const { QSet<int> res; if (lookup != EnvLookup::NoBase && m_base) { @@ -1562,7 +1474,7 @@ std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &ur return it->value(majorVersion); // null shared_ptr is fine if no match } -DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(DomItem &self, QString uri, int majorVersion, EnvLookup options) const +DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion, EnvLookup options) const { std::shared_ptr<ModuleIndex> res; if (options != EnvLookup::BaseOnly) @@ -1594,10 +1506,9 @@ DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(DomI } } -std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri, - int majorVersion, EnvLookup options, - Changeable changeable, - ErrorHandler errorHandler) +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 @@ -1636,7 +1547,7 @@ std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, Q 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.find(majorVersion); it != modsNow.end()) + if (auto it = modsNow.constFind(majorVersion); it != modsNow.cend()) return *it; modsNow.insert(majorVersion, newModulePtr); } @@ -1654,30 +1565,20 @@ std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, Q return newModulePtr; } -std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri, +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(DomItem &self, QString path, EnvLookup options) const +DomEnvironment::qmlDirectoryWithPath(const DomItem &, const QString &path, EnvLookup options) const { - if (options != EnvLookup::BaseOnly) { - QMutexLocker l(mutex()); - if (m_qmlDirectoryWithPath.contains(path)) - return m_qmlDirectoryWithPath.value(path); - } - if (options != EnvLookup::NoBase && m_base) { - return m_base->qmlDirectoryWithPath(self, path, options); - } - return {}; + return lookup<QmlDirectory>(path, options); } -QSet<QString> DomEnvironment::qmlDirectoryPaths(DomItem &, EnvLookup options) const +QSet<QString> DomEnvironment::qmlDirectoryPaths(const DomItem &, EnvLookup options) const { return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>( [this] { @@ -1688,20 +1589,12 @@ QSet<QString> DomEnvironment::qmlDirectoryPaths(DomItem &, EnvLookup options) co } std::shared_ptr<ExternalItemInfo<QmldirFile>> -DomEnvironment::qmldirFileWithPath(DomItem &self, QString path, EnvLookup options) const +DomEnvironment::qmldirFileWithPath(const DomItem &, const QString &path, EnvLookup options) const { - if (options != EnvLookup::BaseOnly) { - QMutexLocker l(mutex()); - auto it = m_qmldirFileWithPath.find(path); - if (it != m_qmldirFileWithPath.end()) - return *it; - } - if (options != EnvLookup::NoBase && m_base) - return m_base->qmldirFileWithPath(self, path, options); - return {}; + return lookup<QmldirFile>(path, options); } -QSet<QString> DomEnvironment::qmldirFilePaths(DomItem &, EnvLookup lOptions) const +QSet<QString> DomEnvironment::qmldirFilePaths(const DomItem &, EnvLookup lOptions) const { return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>( [this] { @@ -1711,7 +1604,7 @@ QSet<QString> DomEnvironment::qmldirFilePaths(DomItem &, EnvLookup lOptions) con m_qmldirFileWithPath, lOptions); } -std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(DomItem &self, QString path, +std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(const DomItem &self, const QString &path, EnvLookup options) const { if (auto qmldirFile = qmldirFileWithPath(self, path + QLatin1String("/qmldir"), options)) @@ -1719,7 +1612,7 @@ std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(DomItem &se return qmlDirectoryWithPath(self, path, options); } -QSet<QString> DomEnvironment::qmlDirPaths(DomItem &self, EnvLookup options) const +QSet<QString> DomEnvironment::qmlDirPaths(const DomItem &self, EnvLookup options) const { QSet<QString> res = qmlDirectoryPaths(self, options); const auto qmldirFiles = qmldirFilePaths(self, options); @@ -1737,20 +1630,12 @@ QSet<QString> DomEnvironment::qmlDirPaths(DomItem &self, EnvLookup options) cons } std::shared_ptr<ExternalItemInfo<QmlFile>> -DomEnvironment::qmlFileWithPath(DomItem &self, QString path, EnvLookup options) const +DomEnvironment::qmlFileWithPath(const DomItem &, const QString &path, EnvLookup options) const { - if (options != EnvLookup::BaseOnly) { - QMutexLocker l(mutex()); - auto it = m_qmlFileWithPath.find(path); - if (it != m_qmlFileWithPath.end()) - return *it; - } - if (options != EnvLookup::NoBase && m_base) - return m_base->qmlFileWithPath(self, path, options); - return {}; + return lookup<QmlFile>(path, options); } -QSet<QString> DomEnvironment::qmlFilePaths(DomItem &, EnvLookup lookup) const +QSet<QString> DomEnvironment::qmlFilePaths(const DomItem &, EnvLookup lookup) const { return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>( [this] { @@ -1761,19 +1646,12 @@ QSet<QString> DomEnvironment::qmlFilePaths(DomItem &, EnvLookup lookup) const } std::shared_ptr<ExternalItemInfo<JsFile>> -DomEnvironment::jsFileWithPath(DomItem &self, QString path, EnvLookup options) const +DomEnvironment::jsFileWithPath(const DomItem &, const QString &path, EnvLookup options) const { - if (options != EnvLookup::BaseOnly) { - QMutexLocker l(mutex()); - if (m_jsFileWithPath.contains(path)) - return m_jsFileWithPath.value(path); - } - if (options != EnvLookup::NoBase && m_base) - return m_base->jsFileWithPath(self, path, EnvLookup::Normal); - return {}; + return lookup<JsFile>(path, options); } -QSet<QString> DomEnvironment::jsFilePaths(DomItem &, EnvLookup lookup) const +QSet<QString> DomEnvironment::jsFilePaths(const DomItem &, EnvLookup lookup) const { return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>( [this] { @@ -1784,19 +1662,12 @@ QSet<QString> DomEnvironment::jsFilePaths(DomItem &, EnvLookup lookup) const } std::shared_ptr<ExternalItemInfo<QmltypesFile>> -DomEnvironment::qmltypesFileWithPath(DomItem &self, QString path, EnvLookup options) const +DomEnvironment::qmltypesFileWithPath(const DomItem &, const QString &path, EnvLookup options) const { - if (options != EnvLookup::BaseOnly) { - QMutexLocker l(mutex()); - if (m_qmltypesFileWithPath.contains(path)) - return m_qmltypesFileWithPath.value(path); - } - if (options != EnvLookup::NoBase && m_base) - return m_base->qmltypesFileWithPath(self, path, EnvLookup::Normal); - return {}; + return lookup<QmltypesFile>(path, options); } -QSet<QString> DomEnvironment::qmltypesFilePaths(DomItem &, EnvLookup lookup) const +QSet<QString> DomEnvironment::qmltypesFilePaths(const DomItem &, EnvLookup lookup) const { return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>( [this] { @@ -1807,21 +1678,14 @@ QSet<QString> DomEnvironment::qmltypesFilePaths(DomItem &, EnvLookup lookup) con } std::shared_ptr<ExternalItemInfo<GlobalScope>> -DomEnvironment::globalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) const +DomEnvironment::globalScopeWithName(const DomItem &, const QString &name, + EnvLookup lookupOptions) const { - if (lookupOptions != EnvLookup::BaseOnly) { - QMutexLocker l(mutex()); - auto id = m_globalScopeWithName.find(name); - if (id != m_globalScopeWithName.end()) - return *id; - } - if (lookupOptions != EnvLookup::NoBase && m_base) - return m_base->globalScopeWithName(self, name, lookupOptions); - return {}; + return lookup<GlobalScope>(name, lookupOptions); } std::shared_ptr<ExternalItemInfo<GlobalScope>> -DomEnvironment::ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) +DomEnvironment::ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookupOptions) { if (auto current = globalScopeWithName(self, name, lookupOptions)) return current; @@ -1844,7 +1708,7 @@ DomEnvironment::ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup return {}; } -QSet<QString> DomEnvironment::globalScopeNames(DomItem &, EnvLookup lookupOptions) const +QSet<QString> DomEnvironment::globalScopeNames(const DomItem &, EnvLookup lookupOptions) const { QSet<QString> res; if (lookupOptions != EnvLookup::NoBase && m_base) { @@ -1869,7 +1733,26 @@ QSet<QString> DomEnvironment::globalScopeNames(DomItem &, EnvLookup lookupOption return res; } -void DomEnvironment::addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadInfo) +/*! + \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; @@ -1891,7 +1774,7 @@ void DomEnvironment::addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadIn } } -std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(Path path) const +std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(const Path &path) const { QMutexLocker l(mutex()); return m_loadInfos.value(path); @@ -1909,145 +1792,163 @@ QList<Path> DomEnvironment::loadInfoPaths() const return lInfos.keys(); } -DomItem::Callback DomEnvironment::callbackForQmlDirectory(DomItem &self, Callback loadCallback, - Callback allDirectDepsCallback, - Callback endCallback) +DomItem::Callback DomEnvironment::getLoadCallbackFor(DomType fileType, const Callback &loadCallback) { - return envCallbackForFile<QmlDirectory>(self, &DomEnvironment::m_qmlDirectoryWithPath, - &DomEnvironment::qmlDirectoryWithPath, loadCallback, - allDirectDepsCallback, endCallback); + 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; } -DomItem::Callback DomEnvironment::callbackForQmlFile(DomItem &self, Callback loadCallback, - Callback allDirectDepsCallback, - Callback endCallback) +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) + { - return envCallbackForFile<QmlFile>(self, &DomEnvironment::m_qmlFileWithPath, - &DomEnvironment::qmlFileWithPath, loadCallback, - allDirectDepsCallback, endCallback); } -DomTop::Callback DomEnvironment::callbackForQmltypesFile(DomItem &self, - DomTop::Callback loadCallback, - Callback allDirectDepsCallback, - DomTop::Callback endCallback) -{ - return envCallbackForFile<QmltypesFile>( - self, &DomEnvironment::m_qmltypesFileWithPath, &DomEnvironment::qmltypesFileWithPath, - [loadCallback](Path p, DomItem &oldV, 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); - }, - allDirectDepsCallback, endCallback); +/*! +\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() +{ + // QTBUG-124799: do not create a SemanticAnalysis in a temporary DomEnvironment, and use the one + // from the base environment instead. + if (m_base) { + auto &result = m_base->semanticAnalysis(); + result.setLoadPaths(m_loadPaths); + return result; + } + + if (m_semanticAnalysis) + return *m_semanticAnalysis; + + Q_ASSERT(domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis)); + m_semanticAnalysis = SemanticAnalysis(m_loadPaths); + return *m_semanticAnalysis; } -DomTop::Callback DomEnvironment::callbackForQmldirFile(DomItem &self, DomTop::Callback loadCallback, - Callback allDirectDepsCallback, - DomTop::Callback endCallback) +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)) { - return envCallbackForFile<QmldirFile>(self, &DomEnvironment::m_qmldirFileWithPath, - &DomEnvironment::qmldirFileWithPath, loadCallback, - allDirectDepsCallback, endCallback); } -DomEnvironment::DomEnvironment(QStringList loadPaths, Options options, - shared_ptr<DomUniverse> universe) - : m_options(options), - m_universe(DomUniverse::guaranteeUniverse(universe)), - m_loadPaths(loadPaths), - m_implicitImports(defaultImplicitImports()) -{} +void DomEnvironment::SemanticAnalysis::setLoadPaths(const QStringList &loadPaths) +{ + if (loadPaths == m_importer->importPaths()) + return; + + m_importer->setImportPaths(loadPaths); +} -DomItem DomEnvironment::create(QStringList loadPaths, Options options, DomItem &universe) +std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths, + Options options, + DomCreationOptions domCreationOptions, + const DomItem &universe) { std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>(); - auto envPtr = std::make_shared<DomEnvironment>(loadPaths, options, universePtr); - return DomItem(envPtr); + return std::make_shared<DomEnvironment>(loadPaths, options, domCreationOptions, universePtr); } -DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, - Options options) +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_implicitImports(defaultImplicitImports()), + m_domCreationOptions(domCreationOptions) +{ +} -template<typename T> -std::shared_ptr<ExternalItemInfo<T>> -addExternalItem(std::shared_ptr<T> file, QString key, - QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &map, AddOption option, - QBasicMutex *mutex) +void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options) { - if (!file) - return {}; - auto eInfo = std::make_shared<ExternalItemInfo<T>>( - file, QDateTime::currentDateTimeUtc()); - { - QMutexLocker l(mutex); - auto it = map.find(key); - if (it != map.end()) { - switch (option) { - case AddOption::KeepExisting: - eInfo = *it; - break; - case AddOption::Overwrite: - map.insert(key, eInfo); - break; - } - } else { - map.insert(key, eInfo); - } + addExternalItem(file, file->canonicalFilePath(), options); + if (domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis)) { + const QQmlJSScope::Ptr &handle = + semanticAnalysis().m_importer->importFile(file->canonicalFilePath()); + + // force reset the outdated qqmljsscope in case it was already populated + QDeferredFactory<QQmlJSScope> newFactory(semanticAnalysis().m_importer.get(), + file->canonicalFilePath(), + TypeReader{ weak_from_this() }); + file->setHandleForPopulation(handle); + handle.resetFactory(std::move(newFactory)); } - return eInfo; } -std::shared_ptr<ExternalItemInfo<QmlFile>> DomEnvironment::addQmlFile(std::shared_ptr<QmlFile> file, - AddOption options) +void DomEnvironment::addQmlDirectory(const std::shared_ptr<QmlDirectory> &file, AddOption options) { - return addExternalItem<QmlFile>(file, file->canonicalFilePath(), m_qmlFileWithPath, options, - mutex()); + addExternalItem(file, file->canonicalFilePath(), options); } -std::shared_ptr<ExternalItemInfo<QmlDirectory>> -DomEnvironment::addQmlDirectory(std::shared_ptr<QmlDirectory> file, AddOption options) +void DomEnvironment::addQmldirFile(const std::shared_ptr<QmldirFile> &file, AddOption options) { - return addExternalItem<QmlDirectory>(file, file->canonicalFilePath(), m_qmlDirectoryWithPath, - options, mutex()); + addExternalItem(file, file->canonicalFilePath(), options); } -std::shared_ptr<ExternalItemInfo<QmldirFile>> -DomEnvironment::addQmldirFile(std::shared_ptr<QmldirFile> file, AddOption options) +void DomEnvironment::addQmltypesFile(const std::shared_ptr<QmltypesFile> &file, AddOption options) { - return addExternalItem<QmldirFile>(file, file->canonicalFilePath(), m_qmldirFileWithPath, - options, mutex()); + addExternalItem(file, file->canonicalFilePath(), options); } -std::shared_ptr<ExternalItemInfo<QmltypesFile>> -DomEnvironment::addQmltypesFile(std::shared_ptr<QmltypesFile> file, AddOption options) +void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options) { - return addExternalItem<QmltypesFile>(file, file->canonicalFilePath(), m_qmltypesFileWithPath, - options, mutex()); + addExternalItem(file, file->canonicalFilePath(), options); } -std::shared_ptr<ExternalItemInfo<JsFile>> DomEnvironment::addJsFile(std::shared_ptr<JsFile> file, - AddOption options) +void DomEnvironment::addGlobalScope(const std::shared_ptr<GlobalScope> &scope, AddOption options) { - return addExternalItem<JsFile>(file, file->canonicalFilePath(), m_jsFileWithPath, options, - mutex()); + addExternalItem(scope, scope->name(), options); } -std::shared_ptr<ExternalItemInfo<GlobalScope>> -DomEnvironment::addGlobalScope(std::shared_ptr<GlobalScope> scope, AddOption options) +QList<QQmlJS::DiagnosticMessage> +DomEnvironment::TypeReader::operator()(QQmlJSImporter *importer, const QString &filePath, + const QSharedPointer<QQmlJSScope> &scopeToPopulate) { - return addExternalItem<GlobalScope>(scope, scope->name(), m_globalScopeWithName, options, - mutex()); + Q_UNUSED(importer); + Q_UNUSED(scopeToPopulate); + + const QFileInfo info{ filePath }; + const QString baseName = info.baseName(); + scopeToPopulate->setInternalName(baseName.endsWith(QStringLiteral(".ui")) ? baseName.chopped(3) + : baseName); + + std::shared_ptr<DomEnvironment> envPtr = m_env.lock(); + // 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(filePath); + if (it == envPtr->m_qmlFileWithPath.constEnd()) { + qCDebug(domLog) << "Import visitor tried to lazily load file \"" << filePath + << "\", but that file was not found in the DomEnvironment. Was this " + "file not discovered by the Dom's dependency loading mechanism?"; + return { QQmlJS::DiagnosticMessage{ + u"Could not find file \"%1\" in the Dom."_s.arg(filePath), QtMsgType::QtWarningMsg, + SourceLocation{} } }; + } + const DomItem qmlFile = it.value()->currentItem(DomItem(envPtr)); + envPtr->populateFromQmlFile(MutableDomItem(qmlFile)); + return {}; } -bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> validEnvPtr) + +bool DomEnvironment::commitToBase( + const DomItem &self, const shared_ptr<DomEnvironment> &validEnvPtr) { if (!base()) return false; @@ -2059,6 +1960,7 @@ bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> vali 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; + std::optional<SemanticAnalysis> my_semanticAnalysis; { QMutexLocker l(mutex()); my_moduleIndexWithUri = m_moduleIndexWithUri; @@ -2069,9 +1971,11 @@ bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> vali my_jsFileWithPath = m_jsFileWithPath; my_qmltypesFileWithPath = m_qmltypesFileWithPath; my_loadInfos = m_loadInfos; + my_semanticAnalysis = semanticAnalysis(); } { QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock? + m_base->m_semanticAnalysis = my_semanticAnalysis; m_base->m_globalScopeWithName.insert(my_globalScopeWithName); m_base->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath); m_base->m_qmldirFileWithPath.insert(my_qmldirFileWithPath); @@ -2099,28 +2003,31 @@ bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> vali } } } - if (validEnvPtr) { + if (validEnvPtr) + m_lastValidBase = validEnvPtr; + if (m_lastValidBase) { QMutexLocker lValid( - validEnvPtr->mutex()); // be more careful about makeCopy calls with lock? - validEnvPtr->m_globalScopeWithName.insert(my_globalScopeWithName); - validEnvPtr->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath); - validEnvPtr->m_qmldirFileWithPath.insert(my_qmldirFileWithPath); + m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock? + m_lastValidBase->m_semanticAnalysis = std::move(my_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()) - validEnvPtr->m_qmlFileWithPath.insert(it.key(), it.value()); + 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()) - validEnvPtr->m_jsFileWithPath.insert(it.key(), it.value()); + m_lastValidBase->m_jsFileWithPath.insert(it.key(), it.value()); } - validEnvPtr->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath); - validEnvPtr->m_loadInfos.insert(my_loadInfos); + 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 = - validEnvPtr->m_moduleIndexWithUri[it.key()]; + 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()); @@ -2130,11 +2037,31 @@ bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> vali } } } + + auto newBaseForPopulation = + m_lastValidBase ? m_lastValidBase->weak_from_this() : m_base->weak_from_this(); + // adapt the factory to the use the base or valid environment for unpopulated files, instead of + // the current environment which will very probably be destroyed anytime soon + for (const auto &qmlFile : my_qmlFileWithPath) { + if (!qmlFile || !qmlFile->current) + continue; + QQmlJSScope::ConstPtr handle = qmlFile->current->handleForPopulation(); + if (!handle) + continue; + auto oldFactory = handle.factory(); + if (!oldFactory) + continue; + + const QDeferredFactory<QQmlJSScope> newFactory( + oldFactory->importer(), oldFactory->filePath(), TypeReader{ newBaseForPopulation }); + handle.resetFactory(newFactory); + } return true; } -void DomEnvironment::loadPendingDependencies(DomItem &self) +void DomEnvironment::loadPendingDependencies() { + DomItem self(shared_from_this()); while (true) { Path elToDo; std::shared_ptr<LoadInfo> loadInfo; @@ -2147,7 +2074,7 @@ void DomEnvironment::loadPendingDependencies(DomItem &self) loadInfo = m_loadInfos.value(elToDo); } if (loadInfo) { - auto cleanup = qScopeGuard([this, elToDo, &self] { + auto cleanup = qScopeGuard([this, &elToDo, &self] { QList<Callback> endCallbacks; { QMutexLocker l(mutex()); @@ -2176,12 +2103,12 @@ void DomEnvironment::loadPendingDependencies(DomItem &self) } } -bool DomEnvironment::finishLoadingDependencies(DomItem &self, int waitMSec) +bool DomEnvironment::finishLoadingDependencies(int waitMSec) { bool hasPendingLoads = true; QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(waitMSec); for (int i = 0; i < waitMSec / 10 + 2; ++i) { - loadPendingDependencies(self); + loadPendingDependencies(); auto lInfos = loadInfos(); auto it = lInfos.cbegin(); auto end = lInfos.cend(); @@ -2204,7 +2131,7 @@ bool DomEnvironment::finishLoadingDependencies(DomItem &self, int waitMSec) return !hasPendingLoads; } -void DomEnvironment::addWorkForLoadInfo(Path elementCanonicalPath) +void DomEnvironment::addWorkForLoadInfo(const Path &elementCanonicalPath) { QMutexLocker l(mutex()); m_loadsWithWork.enqueue(elementCanonicalPath); @@ -2224,6 +2151,9 @@ void DomEnvironment::setLoadPaths(const QStringList &v) { QMutexLocker l(mutex()); m_loadPaths = v; + + if (m_semanticAnalysis) + m_semanticAnalysis->setLoadPaths(v); } QStringList DomEnvironment::loadPaths() const @@ -2232,6 +2162,12 @@ QStringList DomEnvironment::loadPaths() const return m_loadPaths; } +QStringList DomEnvironment::qmldirFiles() const +{ + QMutexLocker l(mutex()); + return m_qmldirFileWithPath.keys(); +} + QString DomEnvironment::globalScopeName() const { return m_globalScopeName; @@ -2248,7 +2184,7 @@ QList<Import> DomEnvironment::implicitImports() const return m_implicitImports; } -void DomEnvironment::addAllLoadedCallback(DomItem &self, DomTop::Callback c) +void DomEnvironment::addAllLoadedCallback(const DomItem &self, DomTop::Callback c) { if (c) { bool immediate = false; @@ -2269,14 +2205,58 @@ void DomEnvironment::clearReferenceCache() m_referenceCache.clear(); } -QString ExternalItemInfoBase::canonicalFilePath(DomItem &self) const +void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile) +{ + if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) { + auto logger = std::make_shared<QQmlJSLogger>(); + logger->setFileName(qmlFile.canonicalFilePath()); + logger->setCode(qmlFilePtr->code()); + logger->setSilent(true); + + 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.get(), 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, std::move(logger) }); + } 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(); DomItem currentObj = currentItem(self); return current->canonicalFilePath(currentObj); } -bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { if (!self.dvValueLazyField(visitor, Fields::currentRevision, [this, &self]() { return currentRevision(self); })) @@ -2296,38 +2276,38 @@ bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, DirectVisitor vi return true; } -int ExternalItemInfoBase::currentRevision(DomItem &) const +int ExternalItemInfoBase::currentRevision(const DomItem &) const { return currentItem()->revision(); } -int ExternalItemInfoBase::lastRevision(DomItem &self) const +int ExternalItemInfoBase::lastRevision(const DomItem &self) const { Path p = currentItem()->canonicalPath(); DomItem lastValue = self.universe()[p.mid(1, p.length() - 1)].field(u"revision"); return static_cast<int>(lastValue.value().toInteger(0)); } -int ExternalItemInfoBase::lastValidRevision(DomItem &self) const +int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const { Path p = currentItem()->canonicalPath(); DomItem lastValidValue = self.universe()[p.mid(1, p.length() - 2)].field(u"validItem").field(u"revision"); return static_cast<int>(lastValidValue.value().toInteger(0)); } -QString ExternalItemPairBase::canonicalFilePath(DomItem &) const +QString ExternalItemPairBase::canonicalFilePath(const DomItem &) const { shared_ptr<ExternalOwningItem> current = currentItem(); return current->canonicalFilePath(); } -Path ExternalItemPairBase::canonicalPath(DomItem &) const +Path ExternalItemPairBase::canonicalPath(const DomItem &) const { shared_ptr<ExternalOwningItem> current = currentItem(); return current->canonicalPath().dropTail(); } -bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { if (!self.dvValueLazyField(visitor, Fields::currentIsValid, [this]() { return currentIsValid(); })) @@ -2349,7 +2329,7 @@ bool ExternalItemPairBase::currentIsValid() const return currentItem() == validItem(); } -RefCacheEntry RefCacheEntry::forPath(DomItem &el, Path canonicalPath) +RefCacheEntry RefCacheEntry::forPath(const DomItem &el, const Path &canonicalPath) { DomItem env = el.environment(); std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); @@ -2365,7 +2345,7 @@ RefCacheEntry RefCacheEntry::forPath(DomItem &el, Path canonicalPath) return cached; } -bool RefCacheEntry::addForPath(DomItem &el, Path canonicalPath, const RefCacheEntry &entry, +bool RefCacheEntry::addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry, AddOption addOption) { DomItem env = el.environment(); diff --git a/src/qmldom/qqmldomtop_p.h b/src/qmldom/qqmldomtop_p.h index f73a973906..afdea6b311 100644 --- a/src/qmldom/qqmldomtop_p.h +++ b/src/qmldom/qqmldomtop_p.h @@ -31,43 +31,21 @@ QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + namespace QQmlJS { namespace Dom { -class QMLDOM_EXPORT ParsingTask { -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 final override { return kindValue; } - ExternalItemPairBase(QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0, - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + 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) @@ -76,21 +54,21 @@ public: OwningItem(o), validExposedAt(o.validExposedAt), currentExposedAt(o.currentExposedAt) {} virtual std::shared_ptr<ExternalOwningItem> validItem() const = 0; - virtual DomItem validItem(DomItem &self) const = 0; + virtual DomItem validItem(const DomItem &self) const = 0; virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0; - virtual DomItem currentItem(DomItem &self) const = 0; + virtual DomItem currentItem(const DomItem &self) const = 0; - QString canonicalFilePath(DomItem &) const final override; - Path canonicalPath(DomItem &self) const final override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) final override; - DomItem field(DomItem &self, QStringView name) const final 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(DomItem &self) const + std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) const { return std::static_pointer_cast<ExternalItemPairBase>(doCopy(self)); } @@ -104,9 +82,9 @@ public: void refreshedDataAt(QDateTime tNew) final override { - return OwningItem::refreshedDataAt(tNew); if (currentItem()) currentItem()->refreshedDataAt(tNew); + return OwningItem::refreshedDataAt(tNew); } friend class DomUniverse; @@ -119,7 +97,7 @@ template<class T> class QMLDOM_EXPORT ExternalItemPair final : public ExternalItemPairBase { // all access should have the lock of the DomUniverse containing this protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { return std::make_shared<ExternalItemPair>(*this); } @@ -127,11 +105,12 @@ protected: 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, QTimeZone::UTC), - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0, - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + 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) @@ -141,10 +120,10 @@ public: { } std::shared_ptr<ExternalOwningItem> validItem() const override { return valid; } - DomItem validItem(DomItem &self) const override { return self.copy(valid); } + DomItem validItem(const DomItem &self) const override { return self.copy(valid); } std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; } - DomItem currentItem(DomItem &self) const override { return self.copy(current); } - std::shared_ptr<ExternalItemPair> makeCopy(DomItem &self) const + 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,11 +150,11 @@ public: virtual Path canonicalPath() const = 0; - Path canonicalPath(DomItem &) const override; - DomItem containingObject(DomItem &) const override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + Path canonicalPath(const DomItem &) const override; + DomItem containingObject(const DomItem &) const override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; template<typename T> - void setExtraOwningItem(QString fieldName, std::shared_ptr<T> item) + void setExtraOwningItem(const QString &fieldName, const std::shared_ptr<T> &item) { QMutexLocker l(mutex()); if (!item) @@ -191,55 +170,55 @@ private: QMap<QString, OwnerT> m_extraOwningItems; }; -class QMLDOM_EXPORT DomUniverse final : 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(DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; public: - enum class Option{ - Default, - SingleThreaded - }; - Q_ENUM(Option) - 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(std::shared_ptr<DomUniverse> univ); - static DomItem create(QString universeName, Options options = Option::SingleThreaded); + static std::shared_ptr<DomUniverse> guaranteeUniverse(const std::shared_ptr<DomUniverse> &univ); + static DomItem create(const QString &universeName); Path canonicalPath() const override; using DomTop::canonicalPath; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - std::shared_ptr<DomUniverse> makeCopy(DomItem &self) const + 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(DomItem &self, QString filePath, QString logicalPath, Callback callback, - LoadOptions loadOptions, - std::optional<DomType> fileType = std::optional<DomType>()); - void loadFile(DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, - QDateTime codeDate, Callback callback, LoadOptions loadOptions, - std::optional<DomType> fileType = std::optional<DomType>()); - 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(QString name) const + std::shared_ptr<ExternalItemPair<GlobalScope>> globalScopeWithName(const QString &name) const { QMutexLocker l(mutex()); return m_globalScopeWithName.value(name); } - std::shared_ptr<ExternalItemPair<GlobalScope>> ensureGlobalScopeWithName(QString name) + std::shared_ptr<ExternalItemPair<GlobalScope>> ensureGlobalScopeWithName(const QString &name) { if (auto current = globalScopeWithName(name)) return current; @@ -263,7 +242,7 @@ public: return QSet<QString>(map.keyBegin(), map.keyEnd()); } - std::shared_ptr<ExternalItemPair<QmlDirectory>> qmlDirectoryWithPath(QString path) const + std::shared_ptr<ExternalItemPair<QmlDirectory>> qmlDirectoryWithPath(const QString &path) const { QMutexLocker l(mutex()); return m_qmlDirectoryWithPath.value(path); @@ -278,7 +257,7 @@ public: return QSet<QString>(map.keyBegin(), map.keyEnd()); } - std::shared_ptr<ExternalItemPair<QmldirFile>> qmldirFileWithPath(QString path) const + std::shared_ptr<ExternalItemPair<QmldirFile>> qmldirFileWithPath(const QString &path) const { QMutexLocker l(mutex()); return m_qmldirFileWithPath.value(path); @@ -293,7 +272,7 @@ public: return QSet<QString>(map.keyBegin(), map.keyEnd()); } - std::shared_ptr<ExternalItemPair<QmlFile>> qmlFileWithPath(QString path) const + std::shared_ptr<ExternalItemPair<QmlFile>> qmlFileWithPath(const QString &path) const { QMutexLocker l(mutex()); return m_qmlFileWithPath.value(path); @@ -308,7 +287,7 @@ public: return QSet<QString>(map.keyBegin(), map.keyEnd()); } - std::shared_ptr<ExternalItemPair<JsFile>> jsFileWithPath(QString path) const + std::shared_ptr<ExternalItemPair<JsFile>> jsFileWithPath(const QString &path) const { QMutexLocker l(mutex()); return m_jsFileWithPath.value(path); @@ -323,7 +302,7 @@ public: return QSet<QString>(map.keyBegin(), map.keyEnd()); } - std::shared_ptr<ExternalItemPair<QmltypesFile>> qmltypesFileWithPath(QString path) const + std::shared_ptr<ExternalItemPair<QmltypesFile>> qmltypesFileWithPath(const QString &path) const { QMutexLocker l(mutex()); return m_qmltypesFileWithPath.value(path); @@ -341,37 +320,145 @@ public: 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; 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; - QQueue<ParsingTask> m_queue; }; - 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 final override { return kindValue; } - ExternalItemInfoBase(Path canonicalPath, - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0, - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + 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) @@ -379,22 +466,22 @@ public: ExternalItemInfoBase(const ExternalItemInfoBase &o) = default; virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0; - virtual DomItem currentItem(DomItem &) const = 0; + virtual DomItem currentItem(const DomItem &) const = 0; - QString canonicalFilePath(DomItem &) const final override; + QString canonicalFilePath(const DomItem &) const final override; Path canonicalPath() const { return m_canonicalPath; } - Path canonicalPath(DomItem &) const final override { return canonicalPath(); } - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) final override; - DomItem field(DomItem &self, QStringView name) const final override + 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(DomItem &self) const; - int lastRevision(DomItem &self) const; - int lastValidRevision(DomItem &self) const; + int currentRevision(const DomItem &self) const; + int lastRevision(const DomItem &self) const; + int lastValidRevision(const DomItem &self) const; - std::shared_ptr<ExternalItemInfoBase> makeCopy(DomItem &self) const + std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) const { return std::static_pointer_cast<ExternalItemInfoBase>(doCopy(self)); } @@ -408,12 +495,12 @@ public: void refreshedDataAt(QDateTime tNew) final override { - return OwningItem::refreshedDataAt(tNew); 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); @@ -446,28 +533,29 @@ template<typename T> class ExternalItemInfo final : public ExternalItemInfoBase { protected: - std::shared_ptr<OwningItem> doCopy(DomItem &) const override + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { return std::make_shared<ExternalItemInfo>(*this); } public: constexpr static DomType kindValue = DomType::ExternalItemInfo; - ExternalItemInfo(std::shared_ptr<T> current = std::shared_ptr<T>(), - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), - int derivedFrom = 0, - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + 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(QString canonicalPath) : current(new T(canonicalPath)) { } + ExternalItemInfo(const QString &canonicalPath) : current(new T(canonicalPath)) { } ExternalItemInfo(const ExternalItemInfo &o): ExternalItemInfoBase(o), current(o.current) { } - std::shared_ptr<ExternalItemInfo> makeCopy(DomItem &self) const + std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) const { return std::static_pointer_cast<ExternalItemInfo>(doCopy(self)); } @@ -475,7 +563,7 @@ public: std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; } - DomItem currentItem(DomItem &self) const override { return self.copy(current); } + DomItem currentItem(const DomItem &self) const override { return self.copy(current); } std::shared_ptr<T> current; }; @@ -499,7 +587,7 @@ class QMLDOM_EXPORT LoadInfo final : public OwningItem Q_DECLARE_TR_FUNCTIONS(LoadInfo); protected: - std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; public: constexpr static DomType kindValue = DomType::LoadInfo; @@ -513,9 +601,9 @@ public: Done // fully loaded }; - LoadInfo(Path elPath = Path(), Status status = Status::NotStarted, int nLoaded = 0, + LoadInfo(const Path &elPath = Path(), Status status = Status::NotStarted, int nLoaded = 0, int derivedFrom = 0, - QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) : OwningItem(derivedFrom, lastDataUpdateAt), m_elementCanonicalPath(elPath), m_status(status), @@ -534,23 +622,23 @@ public: } } - Path canonicalPath(DomItem &self) const override; + Path canonicalPath(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - std::shared_ptr<LoadInfo> makeCopy(DomItem &self) const + 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(DomItem &self, ErrorMessage msg) override + void addError(const DomItem &self, ErrorMessage &&msg) override { - self.path(elementCanonicalPath()).addError(msg); + self.path(elementCanonicalPath()).addError(std::move(msg)); } - void addEndCallback(DomItem &self, std::function<void(Path, DomItem &, DomItem &)> callback); + void addEndCallback(const DomItem &self, std::function<void(Path, const DomItem &, const DomItem &)> callback); - void advanceLoad(DomItem &self); - void finishedLoadingDep(DomItem &self, const Dependency &d); - void execEnd(DomItem &self); + void advanceLoad(const DomItem &self); + void finishedLoadingDep(const DomItem &self, const Dependency &d); + void execEnd(const DomItem &self); Status status() const { @@ -595,15 +683,15 @@ public: } private: - void doAddDependencies(DomItem &self); - void addDependency(DomItem &self, const Dependency &dep); + 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, DomItem &, DomItem &)>> m_endCallbacks; + QList<std::function<void(Path, const DomItem &, const DomItem &)>> m_endCallbacks; }; enum class EnvLookup { Normal, NoBase, BaseOnly }; @@ -617,21 +705,31 @@ public: enum class Cached { None, First, All }; Q_ENUM(Cached) - static RefCacheEntry forPath(DomItem &el, Path canonicalPath); - static bool addForPath(DomItem &el, Path canonicalPath, const RefCacheEntry &entry, + 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 +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(DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; +private: + struct TypeReader + { + std::weak_ptr<DomEnvironment> m_env; + + QList<QQmlJS::DiagnosticMessage> + operator()(QQmlJSImporter *importer, const QString &filePath, + const QSharedPointer<QQmlJSScope> &scopeToPopulate); + }; public: enum class Option { Default = 0x0, @@ -651,121 +749,336 @@ public: Path canonicalPath() const override; using DomTop::canonicalPath; - bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; - DomItem field(DomItem &self, QStringView name) const final override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + DomItem field(const DomItem &self, QStringView name) const final override; - std::shared_ptr<DomEnvironment> makeCopy(DomItem &self) const; + std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) const; - void loadFile(DomItem &self, QString filePath, QString logicalPath, Callback loadCallback, - Callback directDepsCallback, Callback endCallback, LoadOptions loadOptions, + void loadFile(const FileToLoad &file, const Callback &callback, std::optional<DomType> fileType = std::optional<DomType>(), - ErrorHandler h = nullptr); - void loadFile(DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, - QDateTime codeDate, Callback loadCallback, Callback directDepsCallback, - Callback endCallback, LoadOptions loadOptions, - std::optional<DomType> fileType = std::optional<DomType>(), - ErrorHandler h = nullptr); - void loadModuleDependency(DomItem &self, QString uri, Version v, - Callback loadCallback = nullptr, Callback endCallback = nullptr, - ErrorHandler = nullptr); - void loadBuiltins(DomItem &self, Callback callback = nullptr, ErrorHandler h = nullptr); + 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; - QSet<QString> moduleIndexUris(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; - QSet<int> moduleIndexMajorVersions(DomItem &self, QString uri, + 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(DomItem &self, QString uri, int majorVersion, + std::shared_ptr<ModuleIndex> moduleIndexWithUri(const DomItem &self, const QString &uri, int majorVersion, EnvLookup lookup, Changeable changeable, - ErrorHandler errorHandler = nullptr); - std::shared_ptr<ModuleIndex> moduleIndexWithUri(DomItem &self, QString uri, int majorVersion, + 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(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; - QSet<QString> qmlDirectoryPaths(DomItem &self, EnvLookup options = EnvLookup::Normal) const; + 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(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; - QSet<QString> qmldirFilePaths(DomItem &self, EnvLookup options = EnvLookup::Normal) const; + 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(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; - QSet<QString> qmlDirPaths(DomItem &self, EnvLookup options = EnvLookup::Normal) const; + 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(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; - QSet<QString> qmlFilePaths(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + 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(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; - QSet<QString> jsFilePaths(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + 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(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; - QSet<QString> qmltypesFilePaths(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + 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(DomItem &self, QString name, EnvLookup lookup = EnvLookup::Normal) const; + globalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookup = EnvLookup::Normal) const; std::shared_ptr<ExternalItemInfo<GlobalScope>> - ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup lookup = EnvLookup::Normal); - QSet<QString> globalScopeNames(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; - - explicit DomEnvironment(QStringList loadPaths, Options options = Option::SingleThreaded, - std::shared_ptr<DomUniverse> universe = nullptr); - explicit DomEnvironment(std::shared_ptr<DomEnvironment> parent, QStringList loadPaths, - Options options = Option::SingleThreaded); + 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 DomItem create(QStringList loadPaths, Options options = Option::SingleThreaded, - DomItem &universe = DomItem::empty); - - std::shared_ptr<ExternalItemInfo<QmlFile>> - addQmlFile(std::shared_ptr<QmlFile> file, AddOption option = AddOption::KeepExisting); - std::shared_ptr<ExternalItemInfo<QmlDirectory>> - addQmlDirectory(std::shared_ptr<QmlDirectory> file, AddOption option = AddOption::KeepExisting); - std::shared_ptr<ExternalItemInfo<QmldirFile>> - addQmldirFile(std::shared_ptr<QmldirFile> file, AddOption option = AddOption::KeepExisting); - std::shared_ptr<ExternalItemInfo<QmltypesFile>> - addQmltypesFile(std::shared_ptr<QmltypesFile> file, AddOption option = AddOption::KeepExisting); - std::shared_ptr<ExternalItemInfo<JsFile>> addJsFile(std::shared_ptr<JsFile> file, - AddOption option = AddOption::KeepExisting); - std::shared_ptr<ExternalItemInfo<GlobalScope>> - addGlobalScope(std::shared_ptr<GlobalScope> file, AddOption option = AddOption::KeepExisting); - - bool commitToBase(DomItem &self, std::shared_ptr<DomEnvironment> validEnv = nullptr); - - void addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadInfo); - std::shared_ptr<LoadInfo> loadInfo(Path path) const; + 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(DomItem &self); - bool finishLoadingDependencies(DomItem &self, int waitMSec = 30000); - void addWorkForLoadInfo(Path elementCanonicalPath); + 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(DomItem &self, Callback c); + 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; - template<typename T> + + void loadFile(const FileToLoad &file, const Callback &loadCallback, const Callback &endCallback, + std::optional<DomType> fileType = std::optional<DomType>(), + const ErrorHandler &h = nullptr); + + void loadModuleDependency(const DomItem &self, const QString &uri, Version v, + Callback loadCallback = nullptr, Callback endCallback = nullptr, + const ErrorHandler & = nullptr); + + template <typename T> QSet<QString> getStrings(function_ref<QSet<QString>()> getBase, const QMap<QString, T> &selfMap, EnvLookup lookup) const; - Callback callbackForQmlDirectory(DomItem &self, Callback loadCallback, - Callback directDepsCallback, Callback endCallback); - Callback callbackForQmlFile(DomItem &self, Callback loadCallback, Callback directDepsCallback, - Callback endCallback); - Callback callbackForQmltypesFile(DomItem &self, Callback loadCallback, - Callback directDepsCallback, Callback endCallback); - Callback callbackForQmldirFile(DomItem &self, Callback loadCallback, - Callback directDepsCallback, Callback endCallback); + 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 {}; + } + + 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(); + } + + 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()); + auto &map = getMutableRefToMap<T>(); + const auto &it = map.find(key); + if (it != map.end() && option == AddOption::KeepExisting) + return; + map.insert(key, eInfo); + } + + 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 @@ -776,11 +1089,12 @@ private: Origin fromBase = FromGlobal; }; // helper function used by the moduleIndexWithUri methods - ModuleLookupResult moduleIndexWithUriHelper(DomItem &self, QString uri, int majorVersion, + 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 QString m_globalScopeName; @@ -797,6 +1111,18 @@ private: 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 index 3f83130234..ae1ec7fb94 100644 --- a/src/qmldom/qqmldomtypesreader.cpp +++ b/src/qmldom/qqmldomtypesreader.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// 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" @@ -9,11 +9,7 @@ #include <QtQml/private/qqmljsparser_p.h> #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsengine_p.h> -#ifdef QMLDOM_STANDALONE -# include "qmlcompiler/qqmljstypedescriptionreader_p.h" -#else -# include <private/qqmljstypedescriptionreader_p.h> -#endif +#include <private/qqmljstypedescriptionreader_p.h> #include <QtCore/qdir.h> @@ -24,15 +20,16 @@ namespace Dom { using namespace QQmlJS::AST; -static ErrorGroups myParseErrors() +static ErrorGroups readerParseErrors() { static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmltypesFile"), NewErrorGroup("Parsing") } }; return errs; } -void QmltypesReader::insertProperty(QQmlJSScope::Ptr jsScope, const QQmlJSMetaProperty &property, - QMap<int, QmlObject> &objs) +void QmltypesReader::insertProperty( + const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property, + QMap<int, QmlObject> &objs) { PropertyDefinition prop; prop.name = property.propertyName(); @@ -49,7 +46,7 @@ void QmltypesReader::insertProperty(QQmlJSScope::Ptr jsScope, const QQmlJSMetaPr prop.notify = property.notify(); if (prop.name.isEmpty() || prop.typeName.isEmpty()) { - addError(myParseErrors() + addError(readerParseErrors() .warning(tr("Property object is missing a name or type script binding.")) .handle()); return; @@ -63,11 +60,11 @@ void QmltypesReader::insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod, MethodInfo methodInfo; // ### confusion between Method and Slot. Method should be removed. switch (metaMethod.methodType()) { - case QQmlJSMetaMethod::Method: - case QQmlJSMetaMethod::Slot: + case QQmlJSMetaMethodType::Method: + case QQmlJSMetaMethodType::Slot: methodInfo.methodType = MethodInfo::MethodType::Method; break; - case QQmlJSMetaMethod::Signal: + case QQmlJSMetaMethodType::Signal: methodInfo.methodType = MethodInfo::MethodType::Signal; break; default: @@ -86,7 +83,7 @@ void QmltypesReader::insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod, int revision = metaMethod.revision(); methodInfo.isConstructor = metaMethod.isConstructor(); if (methodInfo.name.isEmpty()) { - addError(myParseErrors().error(tr("Method or signal is missing a name.")).handle()); + addError(readerParseErrors().error(tr("Method or signal is missing a name.")).handle()); return; } @@ -112,20 +109,26 @@ EnumDecl QmltypesReader::enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum) return res; } -void QmltypesReader::insertComponent(const QQmlJSScope::Ptr &jsScope, +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; - objects.insert(metaRev, QmlObject()); + QmlObject object; + object.setSemanticScope(jsScope); + objects.insert(metaRev, object); + } + if (!hasExports) { + QmlObject object; + object.setSemanticScope(jsScope); + objects.insert(0, object); } - if (!hasExports) - objects.insert(0, QmlObject()); } bool incrementedPath = false; QString prototype; @@ -176,6 +179,7 @@ void QmltypesReader::insertComponent(const QQmlJSScope::Ptr &jsScope, 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; @@ -193,7 +197,7 @@ void QmltypesReader::insertComponent(const QQmlJSScope::Ptr &jsScope, while (it != begin) { --it; if (it.key() < 0) { - addError(myParseErrors().error( + addError(readerParseErrors().error( tr("negative meta revision %1 not supported").arg(it.key()))); } revToPath.insert(it.key(), compPath.field(Fields::objects).index(objectIndex)); @@ -219,6 +223,7 @@ void QmltypesReader::insertComponent(const QQmlJSScope::Ptr &jsScope, 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); @@ -230,7 +235,7 @@ void QmltypesReader::insertComponent(const QQmlJSScope::Ptr &jsScope, } if (comp.name().isEmpty()) { - addError(myParseErrors() + addError(readerParseErrors() .error(tr("Component definition is missing a name binding.")) .handle()); return; @@ -246,14 +251,14 @@ bool QmltypesReader::parse() qmltypesFilePtr()->code()); QStringList dependencies; QList<QQmlJSExportedScope> objects; - m_isValid = reader(&objects, &dependencies); + const bool isValid = reader(&objects, &dependencies); for (const auto &obj : std::as_const(objects)) insertComponent(obj.scope, obj.exports); - qmltypesFilePtr()->setIsValid(m_isValid); - return m_isValid; + qmltypesFilePtr()->setIsValid(isValid); + return isValid; } -void QmltypesReader::addError(ErrorMessage message) +void QmltypesReader::addError(ErrorMessage &&message) { if (message.file.isEmpty()) message.file = qmltypesFile().canonicalFilePath(); diff --git a/src/qmldom/qqmldomtypesreader_p.h b/src/qmldom/qqmldomtypesreader_p.h index 7794ed16ff..dda0b8f5b3 100644 --- a/src/qmldom/qqmldomtypesreader_p.h +++ b/src/qmldom/qqmldomtypesreader_p.h @@ -1,5 +1,5 @@ // Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// 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 @@ -20,13 +20,9 @@ // for Q_DECLARE_TR_FUNCTIONS #include <QtCore/qcoreapplication.h> -#ifdef QMLDOM_STANDALONE -# include "qmlcompiler/qqmljsmetatypes_p.h" -# include "qmlcompiler/qqmljsscope_p.h" -#else -# include <private/qqmljsmetatypes_p.h> -# include <private/qqmljsscope_p.h> -#endif +#include <private/qqmljsmetatypes_p.h> +#include <private/qqmljsscope_p.h> + QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -36,7 +32,7 @@ class QmltypesReader { Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader) public: - explicit QmltypesReader(DomItem qmltypesFile) + explicit QmltypesReader(const DomItem &qmltypesFile) : m_qmltypesFilePtr(qmltypesFile.ownerAs<QmltypesFile>()), m_qmltypesFile(qmltypesFile) { } @@ -44,12 +40,12 @@ public: bool parse(); // static void read private: - void addError(ErrorMessage message); + void addError(ErrorMessage &&message); - void insertProperty(QQmlJSScope::Ptr jsScope, const QQmlJSMetaProperty &property, + 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::Ptr &jsScope, + void insertComponent(const QQmlJSScope::ConstPtr &jsScope, const QList<QQmlJSScope::Export> &exportsList); EnumDecl enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum); @@ -57,11 +53,10 @@ private: DomItem &qmltypesFile() { return m_qmltypesFile; } ErrorHandler handler() { - return [this](ErrorMessage m) { this->addError(m); }; + return [this](const ErrorMessage &m) { this->addError(ErrorMessage(m)); }; } private: - bool m_isValid; std::shared_ptr<QmltypesFile> m_qmltypesFilePtr; DomItem m_qmltypesFile; Path m_currentPath; diff --git a/src/qmldom/standalone/CMakeLists.txt b/src/qmldom/standalone/CMakeLists.txt deleted file mode 100644 index 68def86a20..0000000000 --- a/src/qmldom/standalone/CMakeLists.txt +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) - -include(../../../.cmake.conf) -project(standalone_qmldom LANGUAGES CXX VERSION "${QT_REPO_MODULE_VERSION}") -option(QMLDOM_EXTERNAL_BUILD "If the build is against an external Qt, and not tested inside a build of this Qt" ON) - -if (QMLDOM_EXTERNAL_BUILD) -find_package(Qt6 COMPONENTS Core Qml) -set(CMAKE_AUTOMOC ON) -endif() - -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -if(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") -elseif (MINGW) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj") -endif() - - -add_library(qmldomlib - STATIC - private/qtqmlcompilerexports_p.h - ../../qmlcompiler/qcoloroutput.cpp ../../qmlcompiler/qcoloroutput_p.h - ../../qmlcompiler/qdeferredpointer_p.h - ../../qmlcompiler/qqmljsannotation.cpp ../../qmlcompiler/qqmljsannotation_p.h - ../../qmlcompiler/qqmljsimporter.cpp ../../qmlcompiler/qqmljsimporter_p.h - ../../qmlcompiler/qqmljsimportvisitor.cpp ../../qmlcompiler/qqmljsimportvisitor_p.h - ../../qmlcompiler/qqmljslogger.cpp ../../qmlcompiler/qqmljslogger_p.h - ../../qmlcompiler/qqmljsmetatypes.cpp ../../qmlcompiler/qqmljsmetatypes_p.h - ../../qmlcompiler/qqmljsresourcefilemapper.cpp ../../qmlcompiler/qqmljsresourcefilemapper_p.h - ../../qmlcompiler/qqmljsscope.cpp ../../qmlcompiler/qqmljsscope_p.h - ../../qmlcompiler/qqmljsscopesbyid_p.h - ../../qmlcompiler/qqmljsutils.cpp ../../qmlcompiler/qqmljsutils_p.h - ../../qmlcompiler/qqmljstypedescriptionreader.cpp ../../qmlcompiler/qqmljstypedescriptionreader_p.h - ../../qmlcompiler/qqmljstypereader.cpp ../../qmlcompiler/qqmljstypereader_p.h - ../../qmlcompiler/qqmljstyperesolver.cpp ../../qmlcompiler/qqmljstyperesolver_p.h - ../../qmlcompiler/qqmljsregistercontent.cpp ../../qmlcompiler/qqmljsregistercontent_p.h - ../qqmldom_fwd_p.h - ../qqmldom_global.h - ../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_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 - ../qqmldomitem.cpp ../qqmldomitem_p.h - ../qqmldomlinewriter.cpp ../qqmldomlinewriter_p.h - ../qqmldommock.cpp ../qqmldommock_p.h - ../qqmldommoduleindex.cpp ../qqmldommoduleindex_p.h - ../qqmldomoutwriter.cpp ../qqmldomoutwriter_p.h - ../qqmldompath.cpp ../qqmldompath_p.h - ../qqmldomreformatter.cpp ../qqmldomreformatter_p.h - ../qqmldomscanner.cpp ../qqmldomscanner_p.h - ../qqmldomstringdumper.cpp ../qqmldomstringdumper_p.h - ../qqmldomtop.cpp ../qqmldomtop_p.h - ../qqmldomtypesreader.cpp ../qqmldomtypesreader_p.h -) -if(NOT QMLDOM_EXTERNAL_BUILD) - qt_autogen_tools_initial_setup(qmldomlib) -endif() - -target_compile_definitions(qmldomlib PUBLIC QMLDOM_STANDALONE) - -target_include_directories(qmldomlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../..) -target_link_libraries(qmldomlib - PUBLIC - Qt::CorePrivate - Qt::QmlPrivate -) diff --git a/src/qmldom/standalone/private/qtqmlcompilerexports_p.h b/src/qmldom/standalone/private/qtqmlcompilerexports_p.h deleted file mode 100644 index a8b6d86456..0000000000 --- a/src/qmldom/standalone/private/qtqmlcompilerexports_p.h +++ /dev/null @@ -1,20 +0,0 @@ -// 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 "../qqmldom_global.h" -#ifndef QTQMLCOMPILEREXPORTS_P_H -#define QTQMLCOMPILEREXPORTS_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. -// - -#define Q_QMLCOMPILER_PRIVATE_EXPORT QMLDOM_EXPORT -#endif |