// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QQMLDOMASTCREATOR_P_H #define QQMLDOMASTCREATOR_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qqmldomelements_p.h" #include "qqmldomitem_p.h" #include "qqmldompath_p.h" #include "qqmldomscriptelements_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { class QQmlDomAstCreator final : public AST::Visitor { Q_DECLARE_TR_FUNCTIONS(QQmlDomAstCreator) using AST::Visitor::endVisit; using AST::Visitor::visit; static constexpr const auto className = "QmlDomAstCreator"; class DomValue { public: template DomValue(const T &obj) : kind(T::kindValue), value(obj) { } DomType kind; std::variant 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 static ScriptStackElement from(const T &obj) { if constexpr (std::is_same_v) { 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; Variant value; ScriptElementVariant takeVariant() { Q_ASSERT_X(std::holds_alternative(value), "takeVariant", "Should be a variant, did the parser change?"); return std::get(std::move(value)); } bool isList() const { return std::holds_alternative(value); }; ScriptElements::ScriptList takeList() { Q_ASSERT_X(std::holds_alternative(value), "takeList", "Should be a List, did the parser change?"); return std::get(std::move(value)); } void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { if (auto x = std::get_if(&value)) { x->base()->setSemanticScope(scope); return; } else if (auto x = std::get_if(&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 qmlFilePtr; QVector nodeStack; QList scriptNodeStack; QVector 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 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 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 T ¤t(int idx = 0) { return std::get(currentEl(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 expectedType); void removeCurrentScriptNode(std::optional 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& value); Path pathOfLastScriptNode() const; /*! \internal Helper to create string literals from AST nodes. */ template static std::shared_ptr makeStringLiteral(QStringView value, AstNodeT *ast) { auto myExp = std::make_shared(ast->firstSourceLocation(), ast->lastSourceLocation()); myExp->setLiteralValue(value.toString()); return myExp; } static std::shared_ptr makeStringLiteral(QStringView value, QQmlJS::SourceLocation loc) { auto myExp = std::make_shared(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>> static decltype(auto) makeScriptElement(AstNodeT *ast) { auto myExp = std::make_shared(ast->firstSourceLocation(), ast->lastSourceLocation()); return myExp; } /*! \internal Helper to create generic script elements from AST nodes. \sa makeScriptElement */ template static std::shared_ptr makeGenericScriptElement(AstNodeT *ast, DomType kind) { auto myExp = std::make_shared( ast->firstSourceLocation(), ast->lastSourceLocation()); myExp->setKind(kind); return myExp; } enum UnaryExpressionKind { Prefix, Postfix }; std::shared_ptr makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken, bool hasExpression, UnaryExpressionKind type); static std::shared_ptr makeGenericScriptElement(SourceLocation location, DomType kind) { auto myExp = std::make_shared(location); myExp->setKind(kind); return myExp; } /*! \internal Helper to create script lists from AST nodes. \sa makeScriptElement */ template static decltype(auto) makeScriptList(AstNodeT *ast) { auto myExp = ScriptElements::ScriptList(ast->firstSourceLocation(), ast->lastSourceLocation()); return myExp; } template 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 &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::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::CommaExpression *) override; void endVisit(AST::CommaExpression *) 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; bool visit(AST::NewExpression *) override; void endVisit(AST::NewExpression *) override; bool visit(AST::NewMemberExpression *) override; void endVisit(AST::NewMemberExpression *) 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; void endVisit(AST::TemplateLiteral *) override; void endVisit(AST::TaggedTemplate *) 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; bool visit(AST::RegExpLiteral *) override; bool visit(AST::ThisExpression *) override; bool visit(AST::SuperLiteral *) override; void throwRecursionDepthError() override; bool stackHasScriptVariant() const { return !scriptNodeStack.isEmpty() && !scriptNodeStack.last().isList(); } bool stackHasScriptList() const { return !scriptNodeStack.isEmpty() && scriptNodeStack.last().isList(); } private: template void endVisitForLists(T *list, const std::function &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 using IsInList = std::disjunction...>; template using RequiresCustomIteration = IsInList; 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 void customListIteration(T *t) { static_assert(RequiresCustomIteration::value); for (T* it = t; it; it = it->next) { if constexpr (std::is_same_v) { AST::Node::accept(it->elision, this); AST::Node::accept(it->element, this); } else if constexpr (std::is_same_v) { AST::Node::accept(it->property, this); } else if constexpr (std::is_same_v) { AST::Node::accept(it->element, this); } else if constexpr (std::is_same_v) { AST::Node::accept(it->declaration, this); } else if constexpr (std::is_same_v) { AST::Node::accept(it->expression, this); } else if constexpr (std::is_same_v) { AST::Node::accept(it->elision, this); AST::Node::accept(it->element, this); } else if constexpr (std::is_same_v) { AST::Node::accept(it->expression, this); } else { Q_UNREACHABLE(); } } } static void initMarkerForActiveVisitor(std::optional &inactiveVisitorMarker, AST::Node::Kind nodeKind, bool continueForDom) { inactiveVisitorMarker.emplace(); inactiveVisitorMarker->inactiveVisitorKind = continueForDom ? ScopeCreator : DomCreator; inactiveVisitorMarker->count = 1; inactiveVisitorMarker->nodeKind = nodeKind; }; template bool performListIterationIfRequired(T *t) { if constexpr (RequiresCustomIteration::value) { customListIteration(t); return false; } Q_UNUSED(t); return true; } template 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 void endVisitT(T *t) { if (m_inactiveVisitorMarker && m_inactiveVisitorMarker->nodeKind == t->kind) { m_inactiveVisitorMarker->count -= 1; if (m_inactiveVisitorMarker->count == 0) m_inactiveVisitorMarker.reset(); } if (m_inactiveVisitorMarker) { switch (m_inactiveVisitorMarker->stillActiveVisitorKind()) { case DomCreator: m_domCreator.endVisit(t); return; case ScopeCreator: m_scopeCreator.endVisit(t); return; }; Q_UNREACHABLE(); } setScopeInDomBeforeEndvisit(); m_domCreator.endVisit(t); setScopeInDomAfterEndvisit(); m_scopeCreator.endVisit(t); } QQmlJSScope::Ptr m_root; QQmlJSLogger *m_logger = nullptr; QQmlJSImporter *m_importer = nullptr; QString m_implicitImportDirectory; QQmlJSImportVisitor m_scopeCreator; QQmlDomAstCreator m_domCreator; std::optional m_inactiveVisitorMarker; bool m_enableScriptExpressions = false; bool m_loadFileLazily = false; }; } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE #endif // QQMLDOMASTCREATOR_P_H