// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qqmldomastdumper_p.h"
#include "qqmldomerrormessage_p.h"
#include <QtQml/private/qqmljsast_p.h>
#include <QtCore/QDebug>
#include <QtCore/QString>
#include <QtCore/QTextStream>

QT_BEGIN_NAMESPACE

namespace QQmlJS {
namespace Dom {
using namespace AST;
/*!
\internal
\enum QQmlJS::AstDumperOption

This enum type specifies the options for the AstDumper.
The values can be combined with the '|' operator, and checked using the '&' operator.

\value None
       Default dumping options
\value NoLocations
       Does not dump SourceLocations, allowing one to compare equivalent AST
       generated by code formatted differently
\value NoAnnotations
       Does not dump annotations
\value DumpNode
       Does dump a <Node></Node> in preVisit/postVisit
*/

/*!
\internal
\class QQmlJS::AstDumper
\brief Dumps or compares AST in an xml like format, mostly for testing/debugging

Initialize it with a lambda that dumps a string, and configure it with .setX methods.
If \l{indent} is set to a non zero value the xml is indented by that amount, and
\l{baseIndent} is the initial indent.
If \l{emitNode} is true the node tag is emitted in the preVisit/postVisit.
If \l{emitLocation} is true the SourceLocations are emitted.
If \l{emitAnnotations} is true annotations are emitted

The implementation has unnecessary roundtrips to QString, but it is supposed to be used
for debugging purposes...

Probably you will not use the visitor at all but rather the static method diff or
the qDebug() and ostream operator << that use the visitor...

\fn AstDumper::diff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions opt, int indent)

\brief compares the two AST::Node n1 and n2 and returns a string describing their first difference

If there are no differences the empty string is returned, so .isEmpty() can be use to check
for no differences.
\l{nContext} decides how much context is printed out.

*/

// no export, just a supporting class...
class AstDumper: public AST::BaseVisitor
{
public:
    AstDumper(const std::function<void(QStringView)> &dumper,
              AstDumperOptions options = AstDumperOption::None, int indent = 1, int baseIndent = 0,
              function_ref<QStringView(SourceLocation)> loc2str = &noStr)
        : dumper(dumper), options(options), indent(indent), baseIndent(baseIndent), loc2str(loc2str)
    {
    }

private:
    void start(QStringView str) {
        dumper(QString::fromLatin1(" ").repeated(baseIndent));
        dumper(u"<");
        dumper(str);
        dumper(u">\n");
        baseIndent += indent;
    }

    void stop(QStringView str) {
        baseIndent -= indent;
        dumper(QString::fromLatin1(" ").repeated(baseIndent));
        dumper(u"</");
        dumper(str);
        dumper(u">\n");
    }

    QString quotedString(const QString &s) {
        QString res(s);
        return QStringLiteral(u"\"") + res
            .replace(QLatin1String("\\"), QLatin1String("\\\\"))
            .replace(QLatin1String("\""), QLatin1String("\\\"")) + QLatin1String("\"");
    }

    QString quotedString(QStringView s) {
        return quotedString(s.toString());
    }

    QString loc(const SourceLocation &s, bool trim = false) {
        QString tokenStr;
        if (s.length > 0)
            tokenStr = loc2str(s).toString()
                .replace(QLatin1String("\\"), QLatin1String("\\\\"))
                .replace(QLatin1String("\""),QLatin1String("\\\""));
        if (trim)
            tokenStr = tokenStr.trimmed();
        if (noLocations() || s == SourceLocation())
            return QLatin1String("\"%1\"").arg(tokenStr);
        else {
            return QLatin1String("\"off:%1 len:%2 l:%3 c:%4 %5\"").arg(QString::number(s.offset), QString::number(s.length), QString::number(s.startLine), QString::number(s.startColumn), tokenStr);
        }
    }

    QString semicolonToken(const SourceLocation &s)
    {
        if (options & AstDumperOption::SloppyCompare)
            return QString();
        return QLatin1String(" semicolonToken=") + loc(s);
    }

    QString boolStr(bool v) { return (v ? quotedString(u"true"): quotedString(u"false")); }
public:
    bool preVisit(Node *) override { if (dumpNode()) start(u"Node"); return true; }
    void postVisit(Node *) override { if (dumpNode()) stop(u"Node"); }

    // Ui
    bool visit(UiProgram *) override { start(u"UiProgram"); return true; }
    void endVisit(AST::UiProgram *) override { stop(u"UiProgram"); }

    bool visit(UiHeaderItemList *) override { start(u"UiHeaderItemList"); return true; }
    void endVisit(AST::UiHeaderItemList *) override { stop(u"UiHeaderItemList"); }

#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
    bool visit(UiPragmaValueList *el) override {
        start(QLatin1String("UiPragmaValueList value=%1").arg(el->value));
        return true;
    }
    void endVisit(AST::UiPragmaValueList *) override { stop(u"UiPragmaValueList"); }
#endif

    bool visit(UiPragma *el) override {
        start(QLatin1String("UiPragma name=%1 pragmaToken=%2%3")
                      .arg(quotedString(el->name), loc(el->pragmaToken),
                           semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::UiPragma *) override { stop(u"UiPragma"); }

    bool visit(UiImport *el) override {
        start(QLatin1String("UiImport fileName=%1 importId=%2 importToken=%3 fileNameToken=%4 "
                            "asToken=%5 importIdToken=%6%7")
                      .arg(quotedString(el->fileName), quotedString(el->importId),
                           loc(el->importToken), loc(el->fileNameToken), loc(el->asToken),
                           loc(el->importIdToken), semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::UiImport *el) override {
        Node::accept(el->version, this);
        stop(u"UiImport");
    }

    bool visit(UiPublicMember *el) override {
        QString typeStr = ((el->type == UiPublicMember::Signal)   ? QLatin1String("Signal") :
                           (el->type == UiPublicMember::Property) ? QLatin1String("Property") : QLatin1String("Unexpected(%1)").arg(QString::number(el->type)));
        start(QLatin1String("UiPublicMember type=%1 typeModifier=%2 name=%3 isDefaultMember=%4 "
                            "isReadonlyMember=%5 isRequired=%6 "
                            "defaultToken=%7 readonlyToken=%8 propertyToken=%9 requiredToken=%10 "
                            "typeModifierToken=%11 typeToken=%12 "
                            "identifierToken=%13 colonToken=%14%15")
                      .arg(quotedString(typeStr), quotedString(el->typeModifier),
                           quotedString(el->name), boolStr(el->isDefaultMember()),
                           boolStr(el->isReadonly()), boolStr(el->isRequired()),
                           loc(el->defaultToken()), loc(el->readonlyToken()),
                           loc(el->propertyToken()), loc(el->requiredToken()),
                           loc(el->typeModifierToken), loc(el->typeToken),
                           loc(el->identifierToken), loc(el->colonToken),
                           semicolonToken(el->semicolonToken)));
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        Node::accept(el->memberType, this);
        return true;
    }
    void endVisit(AST::UiPublicMember *el) override {
        Node::accept(el->parameters, this);
        stop(u"UiPublicMember");
    }

    bool visit(AST::UiSourceElement *el) override {
        start(u"UiSourceElement");
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiSourceElement *) override { stop(u"UiSourceElement"); }

    bool visit(AST::UiObjectDefinition *el) override {
        start(u"UiObjectDefinition");
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiObjectDefinition *) override { stop(u"UiObjectDefinition"); }

    bool visit(AST::UiObjectInitializer *el) override {
        start(QLatin1String("UiObjectInitializer lbraceToken=%1 rbraceToken=%2")
              .arg(loc(el->lbraceToken), loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::UiObjectInitializer *) override { stop(u"UiObjectInitializer"); }

    bool visit(AST::UiObjectBinding *el) override {
        start(QLatin1String("UiObjectBinding colonToken=%1 hasOnToken=%2")
              .arg(loc(el->colonToken), boolStr(el->hasOnToken)));
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiObjectBinding *) override { stop(u"UiObjectBinding"); }

    bool visit(AST::UiScriptBinding *el) override {
        start(QLatin1String("UiScriptBinding colonToken=%1")
              .arg(loc(el->colonToken)));
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiScriptBinding *) override { stop(u"UiScriptBinding"); }

    bool visit(AST::UiArrayBinding *el) override {
        start(QLatin1String("UiArrayBinding colonToken=%1 lbracketToken=%2 rbracketToken=%3")
              .arg(loc(el->colonToken), loc(el->lbracketToken), loc(el->rbracketToken)));
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiArrayBinding *) override { stop(u"UiArrayBinding"); }

    bool visit(AST::UiParameterList *el) override {
        start(QLatin1String("UiParameterList name=%1 commaToken=%2 propertyTypeToken=%3 identifierToken=%4 colonToken=%5")
              .arg(quotedString(el->name), loc(el->commaToken), loc(el->propertyTypeToken), loc(el->identifierToken), loc(el->colonToken)));
        Node::accept(el->type, this);
        return true;
    }
    void endVisit(AST::UiParameterList *el) override {
        stop(u"UiParameterList");
        Node::accept(el->next, this); // put other args at the same level as this one...
    }

    bool visit(AST::UiObjectMemberList *) override { start(u"UiObjectMemberList"); return true; }
    void endVisit(AST::UiObjectMemberList *) override { stop(u"UiObjectMemberList"); }

    bool visit(AST::UiArrayMemberList *el) override {
        start(QLatin1String("UiArrayMemberList commaToken=%1")
              .arg(loc(el->commaToken)));
        return true;
    }
    void endVisit(AST::UiArrayMemberList *) override { stop(u"UiArrayMemberList"); }

    bool visit(AST::UiQualifiedId *el) override {
        start(QLatin1String("UiQualifiedId name=%1 identifierToken=%2")
              .arg(quotedString(el->name), loc(el->identifierToken)));
        Node::accept(el->next, this);
        return true;
    }
    void endVisit(AST::UiQualifiedId *) override { stop(u"UiQualifiedId"); }

    bool visit(AST::UiEnumDeclaration *el) override {
        start(QLatin1String("UiEnumDeclaration enumToken=%1 rbraceToken=%2 name=%3")
              .arg(loc(el->enumToken), loc(el->rbraceToken), quotedString(el->name)));
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiEnumDeclaration *) override { stop(u"UiEnumDeclaration"); }

    bool visit(AST::UiEnumMemberList *el) override {
        start(QLatin1String("UiEnumMemberList member=%1 value=%2 memberToken=%3 valueToken=%4")
              .arg(quotedString(el->member), quotedString(QString::number(el->value)), loc(el->memberToken), loc(el->valueToken)));
        return true;
    }
    void endVisit(AST::UiEnumMemberList *el) override {
        stop(u"UiEnumMemberList");
        Node::accept(el->next, this); // put other enum members at the same level as this one...
    }

    bool visit(AST::UiVersionSpecifier *el) override {
        start(QLatin1String("UiVersionSpecifier majorVersion=%1 minorVersion=%2 majorToken=%3 minorToken=%4")
              .arg(quotedString(QString::number(el->version.majorVersion())),
                   quotedString(QString::number(el->version.minorVersion())),
                   loc(el->majorToken), loc(el->minorToken)));
        return true;
    }
    void endVisit(AST::UiVersionSpecifier *) override { stop(u"UiVersionSpecifier"); }

    bool visit(AST::UiInlineComponent *el) override {
        start(QLatin1String("UiInlineComponent name=%1 componentToken=%2")
              .arg(quotedString(el->name), loc(el->componentToken)));
        if (!noAnnotations()) // put annotations inside the node they refer to
            Node::accept(el->annotations, this);
        return true;
    }
    void endVisit(AST::UiInlineComponent *) override { stop(u"UiInlineComponent"); }

    bool visit(UiRequired *el) override {
        start(QLatin1String("UiRequired name=%1 requiredToken=%2%3")
                      .arg(quotedString(el->name), loc(el->requiredToken),
                           semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(UiRequired *) override { stop(u"UiRequired"); }

    bool visit(UiAnnotation *) override {
        start(u"UiAnnotation");
        return true;
    }
    void endVisit(UiAnnotation *) override { stop(u"UiAnnotation"); }

    bool visit(UiAnnotationList *) override {
        start(u"UiAnnotationList");
        return true;
    }
    void endVisit(UiAnnotationList *) override { stop(u"UiAnnotationList"); }

    // QQmlJS
    bool visit(AST::TypeExpression *) override {
        start(u"TypeExpression");
        return true;
    }
    void endVisit(AST::TypeExpression *) override { stop(u"TypeExpression"); }

    bool visit(AST::ThisExpression *el) override {
        start(QLatin1String("ThisExpression thisToken=%1")
              .arg(loc(el->thisToken)));
        return true;
    }
    void endVisit(AST::ThisExpression *) override { stop(u"ThisExpression"); }

    bool visit(AST::IdentifierExpression *el) override {
        start(QLatin1String("IdentifierExpression name=%1 identifierToken=%2")
              .arg(quotedString(el->name), loc(el->identifierToken)));
        return true;
    }
    void endVisit(AST::IdentifierExpression *) override { stop(u"IdentifierExpression"); }

    bool visit(AST::NullExpression *el) override {
        start(QLatin1String("NullExpression nullToken=%1")
              .arg(loc(el->nullToken)));
        return true;
    }
    void endVisit(AST::NullExpression *) override { stop(u"NullExpression"); }

    bool visit(AST::TrueLiteral *el) override {
        start(QLatin1String("TrueLiteral trueToken=%1")
              .arg(loc(el->trueToken)));
        return true;
    }
    void endVisit(AST::TrueLiteral *) override { stop(u"TrueLiteral"); }

    bool visit(AST::FalseLiteral *el) override {
        start(QLatin1String("FalseLiteral falseToken=%1")
              .arg(loc(el->falseToken)));
        return true;
    }
    void endVisit(AST::FalseLiteral *) override { stop(u"FalseLiteral"); }

    bool visit(AST::SuperLiteral *el) override {
        start(QLatin1String("SuperLiteral superToken=%1")
              .arg(loc(el->superToken)));
        return true;
    }
    void endVisit(AST::SuperLiteral *) override { stop(u"SuperLiteral"); }

    bool visit(AST::StringLiteral *el) override {
        start(QLatin1String("StringLiteral value=%1 literalToken=%2")
              .arg(quotedString(el->value), loc(el->literalToken)));
        return true;
    }
    void endVisit(AST::StringLiteral *) override { stop(u"StringLiteral"); }

    bool visit(AST::TemplateLiteral *el) override {
        start(QLatin1String("TemplateLiteral value=%1 rawValue=%2 literalToken=%3")
              .arg(quotedString(el->value), quotedString(el->rawValue), loc(el->literalToken)));
        Node::accept(el->expression, this);
        return true;
    }
    void endVisit(AST::TemplateLiteral *) override { stop(u"TemplateLiteral"); }

    bool visit(AST::NumericLiteral *el) override {
        start(QLatin1String("NumericLiteral value=%1 literalToken=%2")
              .arg(quotedString(QString::number(el->value)), loc(el->literalToken)));
        return true;
    }
    void endVisit(AST::NumericLiteral *) override { stop(u"NumericLiteral"); }

    bool visit(AST::RegExpLiteral *el) override {
        start(QLatin1String("RegExpLiteral pattern=%1 flags=%2 literalToken=%3")
              .arg(quotedString(el->pattern), quotedString(QString::number(el->flags, 16)), loc(el->literalToken)));
        return true;
    }
    void endVisit(AST::RegExpLiteral *) override { stop(u"RegExpLiteral"); }

    bool visit(AST::ArrayPattern *el) override {
        start(QLatin1String("ArrayPattern lbracketToken=%1 commaToken=%2 rbracketToken=%3 parseMode=%4")
              .arg(loc(el->lbracketToken),loc(el->commaToken),loc(el->rbracketToken), quotedString(QString::number(el->parseMode, 16))));
        return true;
    }
    void endVisit(AST::ArrayPattern *) override { stop(u"ArrayPattern"); }

    bool visit(AST::ObjectPattern *el) override {
        start(QLatin1String("ObjectPattern lbraceToken=%1 rbraceToken=%2 parseMode=%3")
              .arg(loc(el->lbraceToken), loc(el->rbraceToken), quotedString(QString::number(el->parseMode, 16))));
        return true;
    }
    void endVisit(AST::ObjectPattern *) override { stop(u"ObjectPattern"); }

    bool visit(AST::PatternElementList *) override { start(u"PatternElementList"); return true; }
    void endVisit(AST::PatternElementList *) override { stop(u"PatternElementList"); }

    bool visit(AST::PatternPropertyList *) override { start(u"PatternPropertyList"); return true; }
    void endVisit(AST::PatternPropertyList *) override { stop(u"PatternPropertyList"); }

    bool visit(AST::PatternElement *el) override {
        start(QLatin1String("PatternElement identifierToken=%1 bindingIdentifier=%2 type=%3 scope=%4 isForDeclaration=%5")
              .arg(loc(el->identifierToken), quotedString(el->bindingIdentifier), quotedString(QString::number(el->type, 16)),
                   quotedString(QString::number(static_cast<int>(el->scope), 16)), boolStr(el->isForDeclaration)));
        return true;
    }
    void endVisit(AST::PatternElement *) override { stop(u"PatternElement"); }

    bool visit(AST::PatternProperty *el) override {
        start(QLatin1String("PatternProperty identifierToken=%1 bindingIdentifier=%2 type=%3 scope=%4 isForDeclaration=%5 colonToken=%6")
              .arg(loc(el->identifierToken), quotedString(el->bindingIdentifier), quotedString(QString::number(el->type, 16)),
                   quotedString(QString::number(static_cast<int>(el->scope), 16)), boolStr(el->isForDeclaration), loc(el->colonToken)));
        return true;
    }
    void endVisit(AST::PatternProperty *) override { stop(u"PatternProperty"); }

    bool visit(AST::Elision *el) override {
        start(QLatin1String("Elision commaToken=%1")
              .arg(loc(el->commaToken)));
        return true;
    }
    void endVisit(AST::Elision *el) override {
        stop(u"Elision");
        Node::accept(el->next, this); // emit other elisions at the same level
    }

    bool visit(AST::NestedExpression *el) override {
        start(QLatin1String("NestedExpression lparenToken=%1 rparenToken=%2")
              .arg(loc(el->lparenToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::NestedExpression *) override { stop(u"NestedExpression"); }

    bool visit(AST::IdentifierPropertyName *el) override {
        if (options & AstDumperOption::SloppyCompare)
            start(QLatin1String("StringLiteralOrIdentifierPropertyName id=%1")
                  .arg(quotedString(el->id)));
        else
            start(QLatin1String("IdentifierPropertyName id=%1 propertyNameToken=%2")
                  .arg(quotedString(el->id), loc(el->propertyNameToken)));
        return true;
    }
    void endVisit(AST::IdentifierPropertyName *) override {
        if (options & AstDumperOption::SloppyCompare)
            stop(u"StringLiteralOrIdentifierPropertyName");
        else
            stop(u"IdentifierPropertyName");
    }

    bool visit(AST::StringLiteralPropertyName *el) override {
        if (options & AstDumperOption::SloppyCompare)
            start(QLatin1String("StringLiteralOrIdentifierPropertyName id=%1")
                  .arg(quotedString(el->id)));
        else
            start(QLatin1String("StringLiteralPropertyName id=%1 propertyNameToken=%2")
                  .arg(quotedString(el->id), loc(el->propertyNameToken)));
        return true;
    }
    void endVisit(AST::StringLiteralPropertyName *) override {
        if (options & AstDumperOption::SloppyCompare)
            stop(u"StringLiteralOrIdentifierPropertyName");
        else
            stop(u"StringLiteralPropertyName");
    }

    bool visit(AST::NumericLiteralPropertyName *el) override {
        start(QLatin1String("NumericLiteralPropertyName id=%1 propertyNameToken=%2")
              .arg(quotedString(QString::number(el->id)),loc(el->propertyNameToken)));
        return true;
    }
    void endVisit(AST::NumericLiteralPropertyName *) override { stop(u"NumericLiteralPropertyName"); }

    bool visit(AST::ComputedPropertyName *) override {
        start(u"ComputedPropertyName");
        return true;
    }
    void endVisit(AST::ComputedPropertyName *) override { stop(u"ComputedPropertyName"); }

    bool visit(AST::ArrayMemberExpression *el) override {
        start(QLatin1String("ArrayMemberExpression lbraketToken=%1 rbraketToken=%2")
              .arg(loc(el->lbracketToken), loc(el->rbracketToken)));
        return true;
    }
    void endVisit(AST::ArrayMemberExpression *) override { stop(u"ArrayMemberExpression"); }

    bool visit(AST::FieldMemberExpression *el) override {
        start(QLatin1String("FieldMemberExpression name=%1 dotToken=%2 identifierToken=%3")
              .arg(quotedString(el->name), loc(el->dotToken), loc(el->identifierToken)));
        return true;
    }
    void endVisit(AST::FieldMemberExpression *) override { stop(u"FieldMemberExpression"); }

    bool visit(AST::TaggedTemplate *) override {
        start(u"TaggedTemplate");
        return true;
    }
    void endVisit(AST::TaggedTemplate *) override { stop(u"TaggedTemplate"); }

    bool visit(AST::NewMemberExpression *el) override {
        start(QLatin1String("NewMemberExpression newToken=%1 lparenToken=%2 rparenToken=%3")
              .arg(loc(el->newToken), loc(el->lparenToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::NewMemberExpression *) override { stop(u"NewMemberExpression"); }

    bool visit(AST::NewExpression *el) override {
        start(QLatin1String("NewExpression newToken=%1")
              .arg(loc(el->newToken)));
        return true;
    }
    void endVisit(AST::NewExpression *) override { stop(u"NewExpression"); }

    bool visit(AST::CallExpression *el) override {
        start(QLatin1String("CallExpression lparenToken=%1 rparenToken=%2")
              .arg(loc(el->lparenToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::CallExpression *) override { stop(u"CallExpression"); }

    bool visit(AST::ArgumentList *el) override {
        start(QLatin1String("ArgumentList commaToken=%1 isSpreadElement=%2")
              .arg(loc(el->commaToken), boolStr(el->isSpreadElement)));
        return true;
    }
    void endVisit(AST::ArgumentList *) override { stop(u"ArgumentList"); }

    bool visit(AST::PostIncrementExpression *el) override {
        start(QLatin1String("PostIncrementExpression incrementToken=%1")
              .arg(loc(el->incrementToken)));
        return true;
    }
    void endVisit(AST::PostIncrementExpression *) override { stop(u"PostIncrementExpression"); }

    bool visit(AST::PostDecrementExpression *el) override {
        start(QLatin1String("PostDecrementExpression decrementToken=%1")
              .arg(loc(el->decrementToken)));
        return true;
    }
    void endVisit(AST::PostDecrementExpression *) override { stop(u"PostDecrementExpression"); }

    bool visit(AST::DeleteExpression *el) override {
        start(QLatin1String("DeleteExpression deleteToken=%1")
              .arg(loc(el->deleteToken)));
        return true;
    }
    void endVisit(AST::DeleteExpression *) override { stop(u"DeleteExpression"); }

    bool visit(AST::VoidExpression *el) override {
        start(QLatin1String("VoidExpression voidToken=%1")
              .arg(loc(el->voidToken)));
        return true;
    }
    void endVisit(AST::VoidExpression *) override { stop(u"VoidExpression"); }

    bool visit(AST::TypeOfExpression *el) override {
        start(QLatin1String("TypeOfExpression typeofToken=%1")
              .arg(loc(el->typeofToken)));
        return true;
    }
    void endVisit(AST::TypeOfExpression *) override { stop(u"TypeOfExpression"); }

    bool visit(AST::PreIncrementExpression *el) override {
        start(QLatin1String("PreIncrementExpression incrementToken=%1")
              .arg(loc(el->incrementToken)));
        return true;
    }
    void endVisit(AST::PreIncrementExpression *) override { stop(u"PreIncrementExpression"); }

    bool visit(AST::PreDecrementExpression *el) override {
        start(QLatin1String("PreDecrementExpression decrementToken=%1")
              .arg(loc(el->decrementToken)));
        return true;
    }
    void endVisit(AST::PreDecrementExpression *) override { stop(u"PreDecrementExpression"); }

    bool visit(AST::UnaryPlusExpression *el) override {
        start(QLatin1String("UnaryPlusExpression plusToken=%1")
              .arg(loc(el->plusToken)));
        return true;
    }
    void endVisit(AST::UnaryPlusExpression *) override { stop(u"UnaryPlusExpression"); }

    bool visit(AST::UnaryMinusExpression *el) override {
        start(QLatin1String("UnaryMinusExpression minusToken=%1")
              .arg(loc(el->minusToken)));
        return true;
    }
    void endVisit(AST::UnaryMinusExpression *) override { stop(u"UnaryMinusExpression"); }

    bool visit(AST::TildeExpression *el) override {
        start(QLatin1String("TildeExpression tildeToken=%1")
              .arg(loc(el->tildeToken)));
        return true;
    }
    void endVisit(AST::TildeExpression *) override { stop(u"TildeExpression"); }

    bool visit(AST::NotExpression *el) override {
        start(QLatin1String("NotExpression notToken=%1")
              .arg(loc(el->notToken)));
        return true;
    }
    void endVisit(AST::NotExpression *) override { stop(u"NotExpression"); }

    bool visit(AST::BinaryExpression *el) override {
        start(QLatin1String("BinaryExpression op=%1 operatorToken=%2")
              .arg(quotedString(QString::number(el->op,16)), loc(el->operatorToken)));
        return true;
    }
    void endVisit(AST::BinaryExpression *) override { stop(u"BinaryExpression"); }

    bool visit(AST::ConditionalExpression *el) override {
        start(QLatin1String("ConditionalExpression questionToken=%1 colonToken=%2")
              .arg(loc(el->questionToken), loc(el->colonToken)));
        return true;
    }
    void endVisit(AST::ConditionalExpression *) override { stop(u"ConditionalExpression"); }

    bool visit(AST::CommaExpression *el) override {
        start(QLatin1String("Expression commaToken=%1")
              .arg(loc(el->commaToken)));
        return true;
    }
    void endVisit(AST::CommaExpression *) override { stop(u"Expression"); }

    bool visit(AST::Block *el) override {
        start(QLatin1String("Block lbraceToken=%1 rbraceToken=%2")
              .arg(loc(el->lbraceToken), loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::Block *) override { stop(u"Block"); }

    bool visit(AST::StatementList *) override {
        start(u"StatementList");
        return true;
    }
    void endVisit(AST::StatementList *) override { stop(u"StatementList"); }

    bool visit(AST::VariableStatement *el) override {
        start(QLatin1String("VariableStatement declarationKindToken=%1")
              .arg(loc(el->declarationKindToken)));
        return true;
    }
    void endVisit(AST::VariableStatement *) override { stop(u"VariableStatement"); }

    bool visit(AST::VariableDeclarationList *el) override {
        start(QLatin1String("VariableDeclarationList commaToken=%1")
              .arg(loc(el->commaToken)));
        return true;
    }
    void endVisit(AST::VariableDeclarationList *) override { stop(u"VariableDeclarationList"); }

    bool visit(AST::EmptyStatement *el) override {
        start(QLatin1String("EmptyStatement%1").arg(semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::EmptyStatement *) override { stop(u"EmptyStatement"); }

    bool visit(AST::ExpressionStatement *el) override {
        if (options & AstDumperOption::SloppyCompare)
            start(u"ExpressionStatement");
        else
            start(QLatin1String("ExpressionStatement%1").arg(semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::ExpressionStatement *) override { stop(u"ExpressionStatement"); }

    bool visit(AST::IfStatement *el) override {
        start(QLatin1String("IfStatement ifToken=%1 lparenToken=%2 rparenToken=%3 elseToken=%4")
              .arg(loc(el->ifToken), loc(el->lparenToken), loc(el->rparenToken), loc(el->elseToken)));
        return true;
    }
    void endVisit(AST::IfStatement *) override { stop(u"IfStatement"); }

    bool visit(AST::DoWhileStatement *el) override {
        start(QLatin1String(
                      "DoWhileStatement doToken=%1 whileToken=%2 lparenToken=%3 rparenToken=%4%5")
                      .arg(loc(el->doToken), loc(el->whileToken), loc(el->lparenToken),
                           loc(el->rparenToken), semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::DoWhileStatement *) override { stop(u"DoWhileStatement"); }

    bool visit(AST::WhileStatement *el) override {
        start(QLatin1String("WhileStatement whileToken=%1 lparenToken=%2 rparenToken=%3")
              .arg(loc(el->whileToken), loc(el->lparenToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::WhileStatement *) override { stop(u"WhileStatement"); }

    bool visit(AST::ForStatement *el) override {
        if (options & AstDumperOption::SloppyCompare)
            start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 rparenToken=%5")
                          .arg(loc(el->forToken), loc(el->lparenToken), loc(el->rparenToken)));
        else
            start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 firstSemicolonToken=%3 "
                                "secondSemicolonToken=%4 rparenToken=%5")
                          .arg(loc(el->forToken), loc(el->lparenToken),
                               loc(el->firstSemicolonToken), loc(el->secondSemicolonToken),
                               loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::ForStatement *) override { stop(u"ForStatement"); }

    bool visit(AST::ForEachStatement *el) override {
        start(QLatin1String("ForEachStatement forToken=%1 lparenToken=%2 inOfToken=%3 rparenToken=%4 type=%5")
              .arg(loc(el->forToken), loc(el->lparenToken), loc(el->inOfToken), loc(el->rparenToken), quotedString(QString::number(static_cast<int>(el->type), 16))));
        return true;
    }
    void endVisit(AST::ForEachStatement *) override { stop(u"ForEachStatement"); }

    bool visit(AST::ContinueStatement *el) override {
        start(QLatin1String("ContinueStatement label=%1 continueToken=%2 identifierToken=%3%4")
                      .arg(quotedString(el->label), loc(el->continueToken),
                           loc(el->identifierToken), semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::ContinueStatement *) override { stop(u"ContinueStatement"); }

    bool visit(AST::BreakStatement *el) override {
        start(QLatin1String("BreakStatement label=%1 breakToken=%2 identifierToken=%3%4")
                      .arg(quotedString(el->label), loc(el->breakToken), loc(el->identifierToken),
                           semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::BreakStatement *) override { stop(u"BreakStatement"); }

    bool visit(AST::ReturnStatement *el) override {
        start(QLatin1String("ReturnStatement returnToken=%1%2")
                      .arg(loc(el->returnToken), semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::ReturnStatement *) override { stop(u"ReturnStatement"); }

    bool visit(AST::YieldExpression *el) override {
        start(QLatin1String("YieldExpression isYieldStar=%1 yieldToken=%2")
              .arg(boolStr(el->isYieldStar), loc(el->yieldToken)));
        return true;
    }
    void endVisit(AST::YieldExpression *) override { stop(u"YieldExpression"); }

    bool visit(AST::WithStatement *el) override {
        start(QLatin1String("WithStatement withToken=%1 lparenToken=%2 rparenToken=%3")
              .arg(loc(el->withToken), loc(el->lparenToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::WithStatement *) override { stop(u"WithStatement"); }

    bool visit(AST::SwitchStatement *el) override {
        start(QLatin1String("SwitchStatement switchToken=%1 lparenToken=%2 rparenToken=%3")
              .arg(loc(el->switchToken), loc(el->lparenToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::SwitchStatement *) override { stop(u"SwitchStatement"); }

    bool visit(AST::CaseBlock *el) override {
        start(QLatin1String("CaseBlock lbraceToken=%1 rbraceToken=%2")
              .arg(loc(el->lbraceToken), loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::CaseBlock *) override { stop(u"CaseBlock"); }

    bool visit(AST::CaseClauses *) override {
        start(u"CaseClauses");
        return true;
    }
    void endVisit(AST::CaseClauses *) override { stop(u"CaseClauses"); }

    bool visit(AST::CaseClause *el) override {
        start(QLatin1String("CaseClause caseToken=%1 colonToken=%2")
              .arg(loc(el->caseToken), loc(el->colonToken)));
        return true;
    }
    void endVisit(AST::CaseClause *) override { stop(u"CaseClause"); }

    bool visit(AST::DefaultClause *el) override {
        start(QLatin1String("DefaultClause defaultToken=%1 colonToken=%2")
              .arg(loc(el->defaultToken), loc(el->colonToken)));
        return true;
    }
    void endVisit(AST::DefaultClause *) override { stop(u"DefaultClause"); }

    bool visit(AST::LabelledStatement *el) override {
        start(QLatin1String("LabelledStatement label=%1 identifierToken=%2 colonToken=%3")
              .arg(quotedString(el->label), loc(el->identifierToken), loc(el->colonToken)));
        return true;
    }
    void endVisit(AST::LabelledStatement *) override { stop(u"LabelledStatement"); }

    bool visit(AST::ThrowStatement *el) override {
        start(QLatin1String("ThrowStatement throwToken=%1%2")
                      .arg(loc(el->throwToken), semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::ThrowStatement *) override { stop(u"ThrowStatement"); }

    bool visit(AST::TryStatement *el) override {
        start(QLatin1String("TryStatement tryToken=%1")
              .arg(loc(el->tryToken)));
        return true;
    }
    void endVisit(AST::TryStatement *) override { stop(u"TryStatement"); }

    bool visit(AST::Catch *el) override {
        start(QLatin1String("Catch catchToken=%1 lparenToken=%2 identifierToken=%3 rparenToken=%4")
              .arg(loc(el->catchToken), loc(el->lparenToken), loc(el->identifierToken), loc(el->rparenToken)));
        return true;
    }
    void endVisit(AST::Catch *) override { stop(u"Catch"); }

    bool visit(AST::Finally *el) override {
        start(QLatin1String("Finally finallyToken=%1")
              .arg(loc(el->finallyToken)));
        return true;
    }
    void endVisit(AST::Finally *) override { stop(u"Finally"); }

    bool visit(AST::FunctionDeclaration *el) override {
        start(QLatin1String("FunctionDeclaration name=%1 isArrowFunction=%2 isGenerator=%3 functionToken=%4 "
                            "identifierToken=%5 lparenToken=%6 rparenToken=%7 lbraceToken=%8 rbraceToken=%9")
              .arg(quotedString(el->name), boolStr(el->isArrowFunction), boolStr(el->isGenerator),
                   loc(el->functionToken, options & AstDumperOption::SloppyCompare),
                   loc(el->identifierToken), loc(el->lparenToken), loc(el->rparenToken), loc(el->lbraceToken),
                   loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::FunctionDeclaration *) override { stop(u"FunctionDeclaration"); }

    bool visit(AST::FunctionExpression *el) override {

        QString parentheses = options & AstDumperOption::NoLocations
                ? QLatin1String()
                : QLatin1String(" lparenToken=%1 rparenToken=%2")
                          .arg(loc(el->lparenToken), loc(el->rparenToken));

        start(QLatin1String("FunctionExpression name=%1 isArrowFunction=%2 isGenerator=%3 "
                            "functionToken=%4 "
                            "identifierToken=%5%6 lbraceToken=%7 "
                            "rbraceToken=%8")
                      .arg(quotedString(el->name), boolStr(el->isArrowFunction),
                           boolStr(el->isGenerator),
                           loc(el->functionToken, options & AstDumperOption::SloppyCompare),
                           loc(el->identifierToken), parentheses, loc(el->lbraceToken),
                           loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::FunctionExpression *) override { stop(u"FunctionExpression"); }

    bool visit(AST::FormalParameterList *) override {
        start(u"FormalParameterList");
        return true;
    }
    void endVisit(AST::FormalParameterList *) override { stop(u"FormalParameterList"); }

    bool visit(AST::ClassExpression *el) override {
        start(QLatin1String("ClassExpression name=%1 classToken=%2 identifierToken=%3 lbraceToken=%4 rbraceToken=%5")
              .arg(quotedString(el->name), loc(el->classToken), loc(el->identifierToken), loc(el->lbraceToken), loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::ClassExpression *) override { stop(u"ClassExpression"); }

    bool visit(AST::ClassDeclaration *el) override {
        start(QLatin1String("ClassDeclaration name=%1 classToken=%2 identifierToken=%3 lbraceToken=%4 rbraceToken=%5")
              .arg(quotedString(el->name), loc(el->classToken), loc(el->identifierToken), loc(el->lbraceToken), loc(el->rbraceToken)));
        return true;
    }
    void endVisit(AST::ClassDeclaration *) override { stop(u"ClassDeclaration"); }

    bool visit(AST::ClassElementList *el) override {
        start(QLatin1String("ClassElementList isStatic=%1")
              .arg(boolStr(el->isStatic)));
        return true;
    }
    void endVisit(AST::ClassElementList *) override { stop(u"ClassElementList"); }

    bool visit(AST::Program *) override {
        start(u"Program");
        return true;
    }
    void endVisit(AST::Program *) override { stop(u"Program"); }

    bool visit(AST::NameSpaceImport *el) override {
        start(QLatin1String("NameSpaceImport starToken=%1 importedBindingToken=%2 importedBinding=%3")
              .arg(loc(el->starToken), loc(el->importedBindingToken), quotedString(el->importedBinding)));
        return true;
    }
    void endVisit(AST::NameSpaceImport *) override { stop(u"NameSpaceImport"); }

    bool visit(AST::ImportSpecifier *el) override {
        start(QLatin1String("ImportSpecifier identifierToken=%1 importedBindingToken=%2 identifier=%3 importedBinding=%4")
              .arg(loc(el->identifierToken), loc(el->importedBindingToken), quotedString(el->identifier), quotedString(el->importedBinding)));
        return true;
    }
    void endVisit(AST::ImportSpecifier *) override { stop(u"ImportSpecifier"); }

    bool visit(AST::ImportsList *el) override {
        start(QLatin1String("ImportsList importSpecifierToken=%1")
              .arg(loc(el->importSpecifierToken)));
        return true;
    }
    void endVisit(AST::ImportsList *) override { stop(u"ImportsList"); }

    bool visit(AST::NamedImports *el) override {
        start(QLatin1String("NamedImports leftBraceToken=%1 rightBraceToken=%2")
              .arg(loc(el->leftBraceToken), loc(el->rightBraceToken)));
        return true;
    }
    void endVisit(AST::NamedImports *) override { stop(u"NamedImports"); }

    bool visit(AST::FromClause *el) override {
        start(QLatin1String("FromClause fromToken=%1 moduleSpecifierToken=%2 moduleSpecifier=%3")
              .arg(loc(el->fromToken), loc(el->moduleSpecifierToken), quotedString(el->moduleSpecifier)));
        return true;
    }
    void endVisit(AST::FromClause *) override { stop(u"FromClause"); }

    bool visit(AST::ImportClause *el) override {
        start(QLatin1String("ImportClause importedDefaultBindingToken=%1 importedDefaultBinding=%2")
              .arg(loc(el->importedDefaultBindingToken), quotedString(el->importedDefaultBinding)));
        return true;
    }
    void endVisit(AST::ImportClause *) override { stop(u"ImportClause"); }

    bool visit(AST::ImportDeclaration *el) override {
        start(QLatin1String("ImportDeclaration importToken=%1 moduleSpecifierToken=%2 moduleSpecifier=%3")
              .arg(loc(el->importToken), loc(el->moduleSpecifierToken), quotedString(el->moduleSpecifier)));
        return true;
    }
    void endVisit(AST::ImportDeclaration *) override { stop(u"ImportDeclaration"); }

    bool visit(AST::ExportSpecifier *el) override {
        start(QLatin1String("ExportSpecifier identifierToken=%1 exportedIdentifierToken=%2 identifier=%3 exportedIdentifier=%4")
              .arg(loc(el->identifierToken), loc(el->exportedIdentifierToken), quotedString(el->identifier), quotedString(el->exportedIdentifier)));
        return true;
    }
    void endVisit(AST::ExportSpecifier *) override { stop(u"ExportSpecifier"); }

    bool visit(AST::ExportsList *) override {
        start(u"ExportsList");
        return true;
    }
    void endVisit(AST::ExportsList *) override { stop(u"ExportsList"); }

    bool visit(AST::ExportClause *el) override {
        start(QLatin1String("ExportClause leftBraceToken=%1 rightBraceToken=%2")
              .arg(loc(el->leftBraceToken), loc(el->rightBraceToken)));
        return true;
    }
    void endVisit(AST::ExportClause *) override { stop(u"ExportClause"); }

    bool visit(AST::ExportDeclaration *el) override {
        start(QLatin1String("ExportDeclaration exportToken=%1 exportDefault=%3")
                      .arg(loc(el->exportToken), boolStr(el->exportDefault)));
        return true;
    }
    void endVisit(AST::ExportDeclaration *) override { stop(u"ExportDeclaration"); }

    bool visit(AST::ESModule *) override {
        start(u"ESModule");
        return true;
    }
    void endVisit(AST::ESModule *) override { stop(u"ESModule"); }

    bool visit(AST::DebuggerStatement *el) override {
        start(QLatin1String("DebuggerStatement debuggerToken=%1%2")
                      .arg(loc(el->debuggerToken), semicolonToken(el->semicolonToken)));
        return true;
    }
    void endVisit(AST::DebuggerStatement *) override { stop(u"DebuggerStatement"); }

    bool visit(AST::Type *) override {
        start(u"Type");
        return true;
    }
    void endVisit(AST::Type *) override { stop(u"Type"); }

    bool visit(AST::TypeAnnotation *el) override {
        start(QLatin1String("TypeAnnotation colonToken=%1")
              .arg(loc(el->colonToken)));
        return true;
    }
    void endVisit(AST::TypeAnnotation *) override { stop(u"TypeAnnotation"); }

    void throwRecursionDepthError() override {
        qCWarning(domLog) << "Maximum statement or expression depth exceeded in AstDumper";
    }

private:
    // attributes
    std::function <void (QStringView)> dumper;
    AstDumperOptions options = AstDumperOption::None;
    int indent = 0;
    int baseIndent = 0;
    function_ref<QStringView(SourceLocation)> loc2str;
    bool dumpNode(){
        return options & AstDumperOption::DumpNode;
    }
    bool noLocations() {
        return options & AstDumperOption::NoLocations;
    }
    bool noAnnotations() {
        return options & AstDumperOption::NoAnnotations;
    }
};

QDebug operator<<(QDebug d, AST::Node *n) {
    QDebug noQuote = d.noquote().nospace();
    AstDumper visitor([&noQuote](QStringView s){ noQuote << s; });
    Node::accept(n, &visitor);
    return d;
}

QString lineDiff(QString s1, QString s2, int nContext) {
    QTextStream d1(&s1), d2(&s2);
    QList<QString> preLines(nContext);
    int nLine = 0;
    bool same = true;
    QString l1, l2;
    while (same && !d1.atEnd() && !d2.atEnd()) {
        l1=d1.readLine();
        l2=d2.readLine();
        if (l1 == l2)
            preLines[nLine++ % nContext] = l1;
        else
            same = false;
    }
    QString res;
    QTextStream ss(&res);
    if (!same || !d1.atEnd() || !d2.atEnd()) {
        for (int iline = qMin(nLine, nContext); iline > 0; --iline) {
            ss << QLatin1String(" ") << preLines[(nLine - iline) % nContext] << QLatin1String("\n");
        }
        int iline = 0;
        if (!same) {
            ss << QLatin1String("-") << l1 << QLatin1String("\n");
            ++iline;
        }
        if (same && nContext == 0)
            nContext = 1;
        for (;iline < nContext && !d1.atEnd(); iline ++) {
            l1 = d1.readLine();
            ss << QLatin1String("-") << l1 << QLatin1String("\n");
        }
        iline = 0;
        if (!same) {
            ss << QLatin1String("+") << l2 << QLatin1String("\n");
            ++iline;
        }
        for (;iline < nContext && !d2.atEnd(); iline ++) {
            l2 = d2.readLine();
            ss << QLatin1String("+") << l2 << QLatin1String("\n");
        }
    }
    return res;
}

QString astNodeDiff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions opt, int indent,
                    function_ref<QStringView(SourceLocation)>loc2str1,
                    function_ref<QStringView(SourceLocation)>loc2str2) {
    QString s1, s2;
    QTextStream d1(&s1), d2(&s2);
    AstDumper visitor1=AstDumper([&d1](QStringView s){ d1 << s; }, opt, indent, 0, loc2str1);
    AstDumper visitor2=AstDumper([&d2](QStringView s){ d2 << s; }, opt, indent, 0, loc2str2);
    Node::accept(n1, &visitor1);
    Node::accept(n2, &visitor2);
    d1.flush();
    d2.flush();
    return lineDiff(s1, s2, nContext);
}

void astNodeDumper(const Sink &s, Node *n, AstDumperOptions opt, int indent, int baseIndent,
                   function_ref<QStringView(SourceLocation)>loc2str)
{
    AstDumper visitor=AstDumper(s, opt, indent, baseIndent, loc2str);
    Node::accept(n, &visitor);
}

QString astNodeDump(Node *n, AstDumperOptions opt, int indent, int baseIndent,
                    function_ref<QStringView(SourceLocation)>loc2str)
{
    return dumperToString(
            [n, opt, indent, baseIndent, loc2str = std::move(loc2str)](const Sink &s) {
        astNodeDumper(s, n, opt, indent, baseIndent, std::move(loc2str));
    });
}

} // end namespace Dom
} // end namespace QQmlJS

QT_END_NAMESPACE
