Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qqmldomreformatter_p.h"
++ #include "qqmldomcomments_p.h"
++
++ #include <QtQml/private/qqmljsast_p.h>
++ #include <QtQml/private/qqmljsastvisitor_p.h>
++ #include <QtQml/private/qqmljsengine_p.h>
++ #include <QtQml/private/qqmljslexer_p.h>
++
++ #include <QString>
++ #include <algorithm>
++
++ QT_BEGIN_NAMESPACE
++ namespace QQmlJS {
++ namespace Dom {
++
++ using namespace AST;
++
++ bool ScriptFormatter::preVisit(Node *n)
++ {
++ const CommentedElement *c = comments->commentForNode(n, CommentAnchor{});
++ if (!c)
++ return true;
++
++ if (!c->preComments().empty()) {
++ deferredSpaces = 0;
++ for (const auto &preComment : c->preComments())
++ lw.maybeWriteComment(preComment);
++ }
++
++ postOps[n].append([c, this]() {
++ if (!c->postComments().empty()) {
++ deferredSpaces = 0;
++ for (const auto &postComment : c->postComments())
++ lw.maybeWriteComment(postComment);
++ }
++ });
++ return true;
++ }
++
++ void ScriptFormatter::postVisit(Node *n)
++ {
++ for (auto &op : postOps[n]) {
++ op();
++ }
++ postOps.remove(n);
++ }
++
++ void ScriptFormatter::lnAcceptIndented(Node *node)
++ {
++ int indent = lw.increaseIndent(1);
++ ensureNewline();
++ accept(node);
++ lw.decreaseIndent(1, indent);
++ }
++
++ bool ScriptFormatter::acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline)
++ {
++ if (auto *es = cast<EmptyStatement *>(ast)) {
++ writeOutSemicolon(es);
++ return false;
++ }
++ if (cast<Block *>(ast)) {
++ ensureSpaceIfNoComment();
++ accept(ast);
++ if (finishWithSpaceOrNewline)
++ ensureSpaceIfNoComment();
++ return true;
++ } else {
++ if (finishWithSpaceOrNewline)
++ postOps[ast].append([this]() { ensureNewline(); });
++ lnAcceptIndented(ast);
++ return false;
++ }
++ }
++
++ bool ScriptFormatter::visit(ThisExpression *ast)
++ {
++ out(ast->thisToken);
++ return true;
++ }
++
++ bool ScriptFormatter::visit(NullExpression *ast)
++ {
++ out(ast->nullToken);
++ return true;
++ }
++ bool ScriptFormatter::visit(TrueLiteral *ast)
++ {
++ out(ast->trueToken);
++ return true;
++ }
++ bool ScriptFormatter::visit(FalseLiteral *ast)
++ {
++ out(ast->falseToken);
++ return true;
++ }
++
++ bool ScriptFormatter::visit(IdentifierExpression *ast)
++ {
++ out(ast->identifierToken);
++ return true;
++ }
++ bool ScriptFormatter::visit(StringLiteral *ast)
++ {
++ // correctly handle multiline literals
++ if (ast->literalToken.length == 0)
++ return true;
++ QStringView str = m_script->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)
++ {
++ outWithComments(ast->literalToken, ast);
++ return true;
++ }
++ bool ScriptFormatter::visit(RegExpLiteral *ast)
++ {
++ out(ast->literalToken);
++ return true;
++ }
++
++ bool ScriptFormatter::visit(ArrayPattern *ast)
++ {
++ outWithComments(ast->lbracketToken, ast);
++ int baseIndent = lw.increaseIndent(1);
++ if (ast->elements) {
++ accept(ast->elements);
++ outWithComments(ast->commaToken, ast);
++ auto lastElement = lastListElement(ast->elements);
++ if (lastElement->element && cast<ObjectPattern *>(lastElement->element->initializer)) {
++ ensureNewline();
++ }
++ } else {
++ outWithComments(ast->commaToken, ast);
++ }
++ lw.decreaseIndent(1, baseIndent);
++ outWithComments(ast->rbracketToken, ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ObjectPattern *ast)
++ {
++ outWithComments(ast->lbraceToken, ast);
++ ++expressionDepth;
++ if (ast->properties) {
++ lnAcceptIndented(ast->properties);
++ ensureNewline();
++ }
++ --expressionDepth;
++ outWithComments(ast->rbraceToken, ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(PatternElementList *ast)
++ {
++ for (PatternElementList *it = ast; it; it = it->next) {
++ const bool isObjectInitializer =
++ it->element && cast<ObjectPattern *>(it->element->initializer);
++ if (isObjectInitializer)
++ ensureNewline();
++
++ if (it->elision) {
++ accept(it->elision);
++ if (it->element)
++ ensureSpaceIfNoComment();
++ }
++ if (it->element)
++ accept(it->element);
++ if (it->next) {
++ outWithComments(it->next->commaToken, it);
++ ensureSpaceIfNoComment();
++ if (isObjectInitializer)
++ ensureNewline();
++ }
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(PatternPropertyList *ast)
++ {
++ for (PatternPropertyList *it = ast; it; it = it->next) {
++ accept(it->property);
++ if (it->next) {
++ out(",");
++ ensureNewline();
++ }
++ }
++ return false;
++ }
++
++ 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
++ // hence visit(FunctionDeclaration*) is not quite appropriate here
++ if (property->type == PatternProperty::Getter) {
++ out("get");
++ ensureSpaceIfNoComment();
++ } else if (property->type == PatternProperty::Setter) {
++ out("set");
++ ensureSpaceIfNoComment();
++ }
++ FunctionExpression *f = AST::cast<FunctionExpression *>(property->initializer);
++ if (f->isGenerator) {
++ out("*");
++ }
++ accept(property->name);
++ out(f->lparenToken);
++ accept(f->formals);
++ out(f->rparenToken);
++ ensureSpaceIfNoComment();
++ out(f->lbraceToken);
++ const bool scoped = f->lbraceToken.isValid();
++ if (scoped)
++ ++expressionDepth;
++ if (f->body) {
++ if (f->body->next || scoped) {
++ lnAcceptIndented(f->body);
++ lw.newline();
++ } else {
++ auto baseIndent = lw.increaseIndent(1);
++ accept(f->body);
++ lw.decreaseIndent(1, baseIndent);
++ }
++ }
++ if (scoped)
++ --expressionDepth;
++ out(f->rbraceToken);
++ return false;
++ }
++
++ // IdentifierReference[?Yield]
++ accept(property->name);
++ bool useInitializer = false;
++ const bool bindingIdentifierExist = !property->bindingIdentifier.isEmpty();
++ if (property->colonToken.isValid()) {
++ // PropertyName[?Yield] : AssignmentExpression[In, ?Yield]
++ out(":");
++ ensureSpaceIfNoComment();
++ useInitializer = true;
++ if (bindingIdentifierExist)
++ out(property->bindingIdentifier);
++ if (property->bindingTarget)
++ accept(property->bindingTarget);
++ }
++
++ if (property->initializer) {
++ // CoverInitializedName[?Yield]
++ if (bindingIdentifierExist) {
++ ensureSpaceIfNoComment();
++ out("=");
++ ensureSpaceIfNoComment();
++ useInitializer = true;
++ }
++ if (useInitializer)
++ accept(property->initializer);
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(NestedExpression *ast)
++ {
++ out(ast->lparenToken);
++ int baseIndent = lw.increaseIndent(1);
++ accept(ast->expression);
++ lw.decreaseIndent(1, baseIndent);
++ out(ast->rparenToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(IdentifierPropertyName *ast)
++ {
++ out(ast->id.toString());
++ return true;
++ }
++ bool ScriptFormatter::visit(StringLiteralPropertyName *ast)
++ {
++ out(ast->propertyNameToken);
++ return true;
++ }
++ bool ScriptFormatter::visit(NumericLiteralPropertyName *ast)
++ {
++ out(QString::number(ast->id));
++ return true;
++ }
++
++ bool ScriptFormatter::visit(TemplateLiteral *ast)
++ {
++ // correctly handle multiline literals
++ if (ast->literalToken.length != 0) {
++ QStringView str = m_script->loc2Str(ast->literalToken);
++ if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
++ out(str.mid(0, 1));
++ lw.indentNextlines = false;
++ out(str.mid(1));
++ lw.indentNextlines = true;
++ } else {
++ out(str);
++ }
++ }
++ accept(ast->expression);
++ return true;
++ }
++
++ bool ScriptFormatter::visit(ArrayMemberExpression *ast)
++ {
++ accept(ast->base);
++ out(ast->optionalToken);
++ out(ast->lbracketToken);
++ int indent = lw.increaseIndent(1);
++ accept(ast->expression);
++ lw.decreaseIndent(1, indent);
++ out(ast->rbracketToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(FieldMemberExpression *ast)
++ {
++ accept(ast->base);
++ out(ast->dotToken);
++ out(ast->identifierToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(NewMemberExpression *ast)
++ {
++ out("new"); // ast->newToken
++ ensureSpaceIfNoComment();
++ accept(ast->base);
++ out(ast->lparenToken);
++ accept(ast->arguments);
++ out(ast->rparenToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(NewExpression *ast)
++ {
++ out("new"); // ast->newToken
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(CallExpression *ast)
++ {
++ accept(ast->base);
++ outWithComments(ast->optionalToken, ast);
++ outWithComments(ast->lparenToken, ast);
++ accept(ast->arguments);
++ outWithComments(ast->rparenToken, ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(PostIncrementExpression *ast)
++ {
++ accept(ast->base);
++ out(ast->incrementToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(PostDecrementExpression *ast)
++ {
++ accept(ast->base);
++ out(ast->decrementToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(PreIncrementExpression *ast)
++ {
++ out(ast->incrementToken);
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(PreDecrementExpression *ast)
++ {
++ out(ast->decrementToken);
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(DeleteExpression *ast)
++ {
++ out("delete"); // ast->deleteToken
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(VoidExpression *ast)
++ {
++ out("void"); // ast->voidToken
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(TypeOfExpression *ast)
++ {
++ out("typeof"); // ast->typeofToken
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(UnaryPlusExpression *ast)
++ {
++ out(ast->plusToken);
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(UnaryMinusExpression *ast)
++ {
++ out(ast->minusToken);
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(TildeExpression *ast)
++ {
++ out(ast->tildeToken);
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(NotExpression *ast)
++ {
++ out(ast->notToken);
++ accept(ast->expression);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(BinaryExpression *ast)
++ {
++ accept(ast->left);
++ ensureSpaceIfNoComment();
++ out(ast->operatorToken);
++ ensureSpaceIfNoComment();
++ accept(ast->right);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ConditionalExpression *ast)
++ {
++ accept(ast->expression);
++ ensureSpaceIfNoComment();
++ out("?"); // ast->questionToken
++ ensureSpaceIfNoComment();
++ accept(ast->ok);
++ ensureSpaceIfNoComment();
++ out(":"); // ast->colonToken
++ ensureSpaceIfNoComment();
++ accept(ast->ko);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(Block *ast)
++ {
++ // write comments manually because we need to indent before writing a potential post comment
++ const CommentedElement *c =
++ comments->commentForNode(ast, CommentAnchor::from(ast->lbraceToken));
++ if (c)
++ writePreComment(c);
++ out(ast->lbraceToken);
++ const int indent = lw.increaseIndent();
++ if (c)
++ writePostComment(c);
++
++ if (ast->statements) {
++ ++expressionDepth;
++ ensureNewline();
++ accept(ast->statements);
++ ensureNewline();
++ --expressionDepth;
++ }
++
++ // write comments manually because we need to write a potential pre-comment before decreasing
++ // the indentation
++ c = comments->commentForNode(ast, CommentAnchor::from(ast->rbraceToken));
++ if (c)
++ writePreComment(c);
++ lw.decreaseIndent(1, indent);
++ out(ast->rbraceToken);
++ if (c)
++ writePostComment(c);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(VariableStatement *ast)
++ {
++ out(ast->declarationKindToken);
++ ensureSpaceIfNoComment();
++ accept(ast->declarations);
++ if (addSemicolons())
++ writeOutSemicolon(ast);
++ 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");
++ ensureSpaceIfNoComment();
++ break;
++ case PatternElement::Setter:
++ out("set");
++ ensureSpaceIfNoComment();
++ 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) {
++ ensureSpaceIfNoComment();
++ outWithComments(ast->equalToken, ast);
++ ensureSpaceIfNoComment();
++ }
++ accept(ast->initializer);
++ }
++ accept(ast->typeAnnotation);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(TypeAnnotation *ast)
++ {
++ out(ast->colonToken);
++ ensureSpaceIfNoComment();
++ accept(ast->type);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(Type *ast)
++ {
++ accept(ast->typeId);
++ if (ast->typeArgument) {
++ outWithComments(ast->lAngleBracketToken, ast);
++ accept(ast->typeArgument);
++ outWithComments(ast->rAngleBracketToken, ast);
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(UiQualifiedId *ast)
++ {
++ for (UiQualifiedId *it = ast; it; it = it->next) {
++ outWithComments(it->dotToken, it);
++ outWithComments(it->identifierToken, it);
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(EmptyStatement *)
++ {
++ lw.lineWriter.ensureSemicolon();
++ return false;
++ }
++
++ bool ScriptFormatter::visit(IfStatement *ast)
++ {
++ out(ast->ifToken);
++ ensureSpaceIfNoComment();
++ 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)) {
++ ensureSpaceIfNoComment();
++ accept(ast->ko);
++ } else {
++ lnAcceptIndented(ast->ko);
++ }
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(DoWhileStatement *ast)
++ {
++ out(ast->doToken);
++ acceptBlockOrIndented(ast->statement, true);
++ out(ast->whileToken);
++ ensureSpaceIfNoComment();
++ outWithComments(ast->lparenToken, ast);
++ accept(ast->expression);
++ outWithComments(ast->rparenToken, ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(WhileStatement *ast)
++ {
++ out(ast->whileToken);
++ ensureSpaceIfNoComment();
++ outWithComments(ast->lparenToken, ast);
++ accept(ast->expression);
++ outWithComments(ast->rparenToken, ast);
++ acceptBlockOrIndented(ast->statement);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ForStatement *ast)
++ {
++ out(ast->forToken);
++ ensureSpaceIfNoComment();
++ outWithComments(ast->lparenToken, ast);
++ if (ast->initialiser) {
++ accept(ast->initialiser);
++ } else if (ast->declarations) {
++ if (auto pe = ast->declarations->declaration) {
++ out(pe->declarationKindToken);
++ ensureSpaceIfNoComment();
++ }
++ bool first = true;
++ for (VariableDeclarationList *it = ast->declarations; it; it = it->next) {
++ if (!std::exchange(first, false)) {
++ out(",");
++ ensureSpaceIfNoComment();
++ }
++ accept(it->declaration);
++ }
++ }
++ // We don't use writeOutSemicolon() here because we need a semicolon unconditionally.
++ // Repeats for the second semicolon token below.
++ out(u";"); // ast->firstSemicolonToken
++ ensureSpaceIfNoComment();
++ accept(ast->condition);
++ out(u";"); // ast->secondSemicolonToken
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ outWithComments(ast->rparenToken, ast);
++ acceptBlockOrIndented(ast->statement);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ForEachStatement *ast)
++ {
++ out(ast->forToken);
++ ensureSpaceIfNoComment();
++ out(ast->lparenToken);
++ if (auto pe = AST::cast<PatternElement *>(ast->lhs)) {
++ out(pe->declarationKindToken);
++ ensureSpaceIfNoComment();
++ }
++ accept(ast->lhs);
++ ensureSpaceIfNoComment();
++ out(ast->inOfToken);
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ out(ast->rparenToken);
++ acceptBlockOrIndented(ast->statement);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ContinueStatement *ast)
++ {
++ out(ast->continueToken);
++ if (!ast->label.isNull()) {
++ ensureSpaceIfNoComment();
++ out(ast->identifierToken);
++ }
++ if (addSemicolons())
++ writeOutSemicolon(ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(BreakStatement *ast)
++ {
++ out(ast->breakToken);
++ if (!ast->label.isNull()) {
++ ensureSpaceIfNoComment();
++ out(ast->identifierToken);
++ }
++ if (addSemicolons())
++ writeOutSemicolon(ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ReturnStatement *ast)
++ {
++ out(ast->returnToken);
++ if (ast->expression) {
++ if (ast->returnToken.length != 0)
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ }
++ if (ast->returnToken.length > 0 && addSemicolons())
++ writeOutSemicolon(ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(YieldExpression *ast)
++ {
++ out(ast->yieldToken);
++ if (ast->isYieldStar)
++ out("*");
++ if (ast->expression) {
++ if (ast->yieldToken.isValid())
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ThrowStatement *ast)
++ {
++ out(ast->throwToken);
++ if (ast->expression) {
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ }
++ if (addSemicolons())
++ writeOutSemicolon(ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(WithStatement *ast)
++ {
++ out(ast->withToken);
++ ensureSpaceIfNoComment();
++ out(ast->lparenToken);
++ accept(ast->expression);
++ out(ast->rparenToken);
++ acceptBlockOrIndented(ast->statement);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(SwitchStatement *ast)
++ {
++ out(ast->switchToken);
++ ensureSpaceIfNoComment();
++ out(ast->lparenToken);
++ accept(ast->expression);
++ out(ast->rparenToken);
++ ensureSpaceIfNoComment();
++ accept(ast->block);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(CaseBlock *ast)
++ {
++ out(ast->lbraceToken);
++ ++expressionDepth;
++ ensureNewline();
++ accept(ast->clauses);
++ if (ast->clauses && ast->defaultClause)
++ ensureNewline();
++ accept(ast->defaultClause);
++ if (ast->moreClauses)
++ ensureNewline();
++ accept(ast->moreClauses);
++ ensureNewline();
++ --expressionDepth;
++ out(ast->rbraceToken);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(CaseClause *ast)
++ {
++ out("case"); // ast->caseToken
++ ensureSpaceIfNoComment();
++ accept(ast->expression);
++ outWithComments(ast->colonToken, ast);
++ 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
++ ensureSpaceIfNoComment();
++ accept(ast->statement);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(TryStatement *ast)
++ {
++ outWithComments(ast->tryToken, ast);
++ ensureSpaceIfNoComment();
++ accept(ast->statement);
++ if (ast->catchExpression) {
++ ensureSpaceIfNoComment();
++ accept(ast->catchExpression);
++ }
++ if (ast->finallyExpression) {
++ ensureSpaceIfNoComment();
++ accept(ast->finallyExpression);
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(Catch *ast)
++ {
++ outWithComments(ast->catchToken, ast);
++ ensureSpaceIfNoComment();
++ outWithComments(ast->lparenToken, ast);
++ outWithComments(ast->identifierToken, ast);
++ outWithComments(ast->rparenToken, ast);
++ ensureSpaceIfNoComment();
++ accept(ast->statement);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(Finally *ast)
++ {
++ outWithComments(ast->finallyToken, ast);
++ ensureSpaceIfNoComment();
++ 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) {
++ outWithComments(ast->functionToken, ast);
++ if (ast->isGenerator)
++ outWithComments(ast->starToken, ast);
++ ensureSpaceIfNoComment();
++ outWithComments(ast->identifierToken, ast);
++ }
++
++ const bool removeParentheses = ast->isArrowFunction && ast->formals && !ast->formals->next
++ && (ast->formals->element && !ast->formals->element->bindingTarget);
++
++ // note: qmlformat removes the parentheses for "(x) => x". In that case, we still need
++ // to print potential comments attached to `(` or `)` via `OnlyComments` option.
++ outWithComments(ast->lparenToken, ast, removeParentheses ? OnlyComments : TokenAndComment);
++ int baseIndent = lw.increaseIndent(1);
++ accept(ast->formals);
++ lw.decreaseIndent(1, baseIndent);
++ outWithComments(ast->rparenToken, ast, removeParentheses ? OnlyComments : TokenAndComment);
++ accept(ast->typeAnnotation);
++ ensureSpaceIfNoComment();
++ if (ast->isArrowFunction) {
++ out("=>");
++ ensureSpaceIfNoComment();
++ }
++ outWithComments(ast->lbraceToken, ast);
++ if (ast->lbraceToken.length != 0)
++ ++expressionDepth;
++ if (ast->body) {
++ if (ast->body->next || ast->lbraceToken.length != 0) {
++ lnAcceptIndented(ast->body);
++ ensureNewline();
++ } else {
++ // print a single statement in one line. E.g. x => x * 2
++ baseIndent = lw.increaseIndent(1);
++ accept(ast->body);
++ lw.decreaseIndent(1, baseIndent);
++ }
++ }
++ if (ast->lbraceToken.length != 0)
++ --expressionDepth;
++ outWithComments(ast->rbraceToken, ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(Elision *ast)
++ {
++ for (Elision *it = ast; it; it = it->next) {
++ if (!it->commaToken.isValid())
++ continue;
++ outWithComments(it->commaToken, it);
++ if (it->next)
++ ensureSpaceIfNoComment();
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(ArgumentList *ast)
++ {
++ for (ArgumentList *it = ast; it; it = it->next) {
++ if (it->commaToken.isValid()) {
++ outWithComments(it->commaToken, it);
++ ensureSpaceIfNoComment();
++ }
++ if (it->spreadToken.isValid())
++ outWithComments(it->spreadToken, it);
++ accept(it->expression);
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(StatementList *ast)
++ {
++ ++expressionDepth;
++ for (StatementList *it = ast; it; it = it->next) {
++ // ### work around parser bug: skip empty statements with wrong tokens
++ if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) {
++ if (m_script->loc2Str(emptyStatement->semicolonToken) != QLatin1String(";"))
++ continue;
++ }
++
++ accept(it->statement);
++ if (it->next) {
++ // There might be a post-comment attached to the current
++ // statement or a pre-comment attached to the next
++ // statmente or both.
++ // If any of those are present they will take care of
++ // handling the spacing between the statements so we
++ // don't need to push any newline.
++ auto *commentForCurrentStatement =
++ comments->commentForNode(it->statement, CommentAnchor{});
++ auto *commentForNextStatement =
++ comments->commentForNode(it->next->statement, CommentAnchor{});
++
++ 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 });
++
++ ensureNewline(lineDelta);
++ }
++ }
++ --expressionDepth;
++ return false;
++ }
++
++ bool ScriptFormatter::visit(VariableDeclarationList *ast)
++ {
++ for (VariableDeclarationList *it = ast; it; it = it->next) {
++ accept(it->declaration);
++ if (it->next) {
++ out(","); // it->commaToken
++ ensureSpaceIfNoComment();
++ }
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(CaseClauses *ast)
++ {
++ for (CaseClauses *it = ast; it; it = it->next) {
++ accept(it->clause);
++ if (it->next)
++ ensureNewline();
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(FormalParameterList *ast)
++ {
++ for (FormalParameterList *it = ast; it; it = it->next) {
++ accept(it->element);
++ if (it->commaToken.isValid()) {
++ outWithComments(it->commaToken, it);
++ ensureSpaceIfNoComment();
++ }
++ }
++ return false;
++ }
++
++ // to check
++ bool ScriptFormatter::visit(SuperLiteral *)
++ {
++ out("super");
++ return true;
++ }
++ bool ScriptFormatter::visit(ComputedPropertyName *)
++ {
++ out("[");
++ return true;
++ }
++ bool ScriptFormatter::visit(CommaExpression *el)
++ {
++ accept(el->left);
++ out(",");
++ ensureSpaceIfNoComment();
++ accept(el->right);
++ return false;
++ }
++ bool ScriptFormatter::visit(ExpressionStatement *el)
++ {
++ if (addSemicolons())
++ postOps[el->expression].append([this, el]() { writeOutSemicolon(el); });
++ return true;
++ }
++
++ // Return false because we want to omit default function calls in accept0 implementation.
++ bool ScriptFormatter::visit(ClassDeclaration *ast)
++ {
++ out(ast->classToken);
++ ensureSpaceIfNoComment();
++ outWithComments(ast->identifierToken, ast);
++ if (ast->heritage) {
++ ensureSpaceIfNoComment();
++ out("extends");
++ ensureSpaceIfNoComment();
++ accept(ast->heritage);
++ }
++ ensureSpaceIfNoComment();
++ outWithComments(ast->lbraceToken, ast);
++ int baseIndent = lw.increaseIndent();
++ for (ClassElementList *it = ast->elements; it; it = it->next) {
++ lw.newline();
++ if (it->isStatic) {
++ out("static");
++ ensureSpaceIfNoComment();
++ }
++ accept(it->property);
++ lw.newline();
++ }
++ lw.decreaseIndent(1, baseIndent);
++ outWithComments(ast->rbraceToken, ast);
++ return false;
++ }
++
++ bool ScriptFormatter::visit(AST::ImportDeclaration *ast)
++ {
++ out(ast->importToken);
++ ensureSpaceIfNoComment();
++ if (!ast->moduleSpecifier.isNull()) {
++ out(ast->moduleSpecifierToken);
++ }
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ImportSpecifier *ast)
++ {
++ if (!ast->identifier.isNull()) {
++ out(ast->identifierToken);
++ ensureSpaceIfNoComment();
++ out("as");
++ ensureSpaceIfNoComment();
++ }
++ out(ast->importedBindingToken);
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::NameSpaceImport *ast)
++ {
++ out(ast->starToken);
++ ensureSpaceIfNoComment();
++ out("as");
++ ensureSpaceIfNoComment();
++ out(ast->importedBindingToken);
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ImportsList *ast)
++ {
++ for (ImportsList *it = ast; it; it = it->next) {
++ accept(it->importSpecifier);
++ if (it->next) {
++ out(",");
++ ensureSpaceIfNoComment();
++ }
++ }
++ return false;
++ }
++ bool ScriptFormatter::visit(AST::NamedImports *ast)
++ {
++ out(ast->leftBraceToken);
++ if (ast->importsList) {
++ ensureSpaceIfNoComment();
++ }
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ImportClause *ast)
++ {
++ if (!ast->importedDefaultBinding.isNull()) {
++ out(ast->importedDefaultBindingToken);
++ if (ast->nameSpaceImport || ast->namedImports) {
++ out(",");
++ ensureSpaceIfNoComment();
++ }
++ }
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ExportDeclaration *ast)
++ {
++ out(ast->exportToken);
++ ensureSpaceIfNoComment();
++ if (ast->exportDefault) {
++ out("default");
++ ensureSpaceIfNoComment();
++ }
++ if (ast->exportsAll()) {
++ out("*");
++ }
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ExportClause *ast)
++ {
++ out(ast->leftBraceToken);
++ if (ast->exportsList) {
++ ensureSpaceIfNoComment();
++ }
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ExportSpecifier *ast)
++ {
++ out(ast->identifier);
++ if (ast->exportedIdentifierToken.isValid()) {
++ ensureSpaceIfNoComment();
++ out("as");
++ ensureSpaceIfNoComment();
++ out(ast->exportedIdentifier);
++ }
++ return true;
++ }
++
++ bool ScriptFormatter::visit(AST::ExportsList *ast)
++ {
++ for (ExportsList *it = ast; it; it = it->next) {
++ accept(it->exportSpecifier);
++ if (it->next) {
++ out(",");
++ ensureSpaceIfNoComment();
++ }
++ }
++ return false;
++ }
++
++ bool ScriptFormatter::visit(AST::FromClause *ast)
++ {
++ ensureSpaceIfNoComment();
++ out(ast->fromToken);
++ ensureSpaceIfNoComment();
++ out(ast->moduleSpecifierToken);
++ return true;
++ }
++
++ void ScriptFormatter::endVisit(ComputedPropertyName *)
++ {
++ out("]");
++ }
++
++ void ScriptFormatter::endVisit(AST::ExportDeclaration *ast)
++ {
++ // add a semicolon at the end of the following expressions
++ // export * FromClause
++ // export ExportClause FromClause ;
++ if (ast->fromClause) {
++ writeOutSemicolon(ast);
++ }
++
++ // add a semicolon at the end of the following expressions
++ // export ExportClause ;
++ if (ast->exportClause && !ast->fromClause) {
++ writeOutSemicolon(ast);
++ }
++
++ // 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)) {
++ writeOutSemicolon(ast);
++ }
++ // ArrowFunction in QQmlJS::AST is handled with the help of FunctionDeclaration
++ // and not as part of AssignmentExpression (as per ECMA
++ if (ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration
++ && static_cast<AST::FunctionDeclaration *>(ast->variableStatementOrDeclaration)
++ ->isArrowFunction) {
++ writeOutSemicolon(ast);
++ }
++ }
++ }
++
++ void ScriptFormatter::endVisit(AST::ExportClause *ast)
++ {
++ if (ast->exportsList) {
++ ensureSpaceIfNoComment();
++ }
++ out(ast->rightBraceToken);
++ }
++
++ void ScriptFormatter::endVisit(AST::NamedImports *ast)
++ {
++ if (ast->importsList) {
++ ensureSpaceIfNoComment();
++ }
++ out(ast->rightBraceToken);
++ }
++
++ void ScriptFormatter::endVisit(AST::ImportDeclaration *id)
++ {
++ writeOutSemicolon(id);
++ }
++
++ void ScriptFormatter::throwRecursionDepthError()
++ {
++ out("/* ERROR: Hit recursion limit ScriptFormatter::visiting AST, rewrite failed */");
++ }
++
++ // Given an existing semicolon, can we safely remove it without changing
++ // behavior Restricted characters (, [, +, - and / are the only ones that can
++ // cause issues when removing a semicolon. The / is a special case because it
++ // can be the start of a comment or a division operator.
++ bool ScriptFormatter::canRemoveSemicolon(AST::Node *node)
++ {
++ const auto canRelyOnASI = [this](Node *node) {
++ auto nodeLoc = node->lastSourceLocation().offset + 1;
++ auto code = m_script->engine()->code();
++ auto it = code.begin() + nodeLoc;
++ auto end = code.end();
++ // Scan through code, skipping comments and whitespace
++ // until we find the next real token to determine if ASI will work.
++ while (it != end) {
++ QChar c = *it;
++ // Skip all whitespace and semicolons
++ if (c.isSpace() || c == u';') {
++ ++it;
++ continue;
++ }
++ // Skip comments
++ if (c == u'/' && it + 1 != end) {
++ auto next = it + 1;
++ if (*next == u'/') {
++ // Single-line comment, skip to end of line
++ it = std::find(next, end, u'\n');
++ if (it != end)
++ ++it;
++ continue;
++ } else if (*next == u'*') {
++ // Multi-line comment, skip to */
++ it = next + 1;
++ while (it != end) {
++ if (*it == u'*' && it + 1 != end && *(it + 1) == u'/') {
++ it += 2;
++ break;
++ }
++ ++it;
++ }
++ continue;
++ }
++ // If / is not followed by / or *, it's a division operator
++ return false;
++ }
++ // Check for restricted characters that can continue the expression
++ if (c == u'(' || c == u'[' || c == u'+' || c == u'-') {
++ // Cannot remove semicolon; restricted char follows
++ return false;
++ }
++ // Found any other token, safe to remove semicolon
++ return true;
++ }
++ // Reached end of code, safe to remove semicolon
++ return true;
++ };
++
++ // Check if the node is a statement that requires a semicolon to avoid ASI issues
++ switch (node->kind) {
++ case AST::Node::Kind_ExpressionStatement:
++ return canRelyOnASI(cast<ExpressionStatement *>(node));
++ case AST::Node::Kind_VariableStatement:
++ return canRelyOnASI(cast<VariableStatement *>(node));
++ case AST::Node::Kind_EmptyStatement:
++ return false;
++ case AST::Node::Kind_ContinueStatement:
++ case AST::Node::Kind_BreakStatement:
++ case AST::Node::Kind_ReturnStatement:
++ case AST::Node::Kind_ThrowStatement:
++ case AST::Node::Kind_ExportDeclaration:
++ case AST::Node::Kind_ImportDeclaration:
++ case AST::Node::Kind_FromClause:
++ case AST::Node::Kind_ExportClause:
++ default:
++ return true;
++ }
++ }
++
++ OutWriter &ScriptFormatter::writeOutSemicolon(AST::Node *node)
++ {
++ if (!node)
++ return lw;
++ switch (lw.lineWriter.options().semicolonRule) {
++ case LineWriterOptions::SemicolonRule::Essential:
++ if (!canRemoveSemicolon(node))
++ out(u";");
++ ensureNewline();
++ return lw;
++ case LineWriterOptions::SemicolonRule::Always:
++ out(u";");
++ return lw;
++ default:
++ Q_UNREACHABLE_RETURN(lw);
++ }
++ }
++
++ void reformatAst(OutWriter &lw, const QQmlJS::Dom::ScriptExpression *const script)
++ {
++ if (script)
++ ScriptFormatter formatter(lw, script);
++ }
++
++ } // namespace Dom
++ } // namespace QQmlJS
++ QT_END_NAMESPACE