diff options
Diffstat (limited to 'src/qmldom/qqmldomreformatter.cpp')
-rw-r--r-- | src/qmldom/qqmldomreformatter.cpp | 135 |
1 files changed, 107 insertions, 28 deletions
diff --git a/src/qmldom/qqmldomreformatter.cpp b/src/qmldom/qqmldomreformatter.cpp index 9576e3c8cb..1f8337ac71 100644 --- a/src/qmldom/qqmldomreformatter.cpp +++ b/src/qmldom/qqmldomreformatter.cpp @@ -10,9 +10,7 @@ #include <QtQml/private/qqmljslexer_p.h> #include <QString> - #include <algorithm> -#include <limits> QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -46,6 +44,10 @@ void ScriptFormatter::lnAcceptIndented(Node *node) bool ScriptFormatter::acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline) { + if (auto *es = cast<EmptyStatement *>(ast)) { + writeOutSemicolon(es); + return false; + } if (cast<Block *>(ast)) { lw.lineWriter.ensureSpace(); accept(ast); @@ -92,7 +94,7 @@ bool ScriptFormatter::visit(StringLiteral *ast) // correctly handle multiline literals if (ast->literalToken.length == 0) return true; - QStringView str = loc2Str(ast->literalToken); + QStringView str = m_script->loc2Str(ast->literalToken); if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) { out(str.mid(0, 1)); lw.indentNextlines = false; @@ -288,7 +290,7 @@ bool ScriptFormatter::visit(TemplateLiteral *ast) { // correctly handle multiline literals if (ast->literalToken.length != 0) { - QStringView str = loc2Str(ast->literalToken); + QStringView str = m_script->loc2Str(ast->literalToken); if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) { out(str.mid(0, 1)); lw.indentNextlines = false; @@ -474,7 +476,7 @@ bool ScriptFormatter::visit(VariableStatement *ast) lw.lineWriter.ensureSpace(); accept(ast->declarations); if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -512,9 +514,9 @@ bool ScriptFormatter::visit(PatternElement *ast) return false; } -bool ScriptFormatter::visit(EmptyStatement *ast) +bool ScriptFormatter::visit(EmptyStatement *) { - out(ast->semicolonToken); + lw.lineWriter.ensureSemicolon(); return false; } @@ -575,14 +577,21 @@ bool ScriptFormatter::visit(ForStatement *ast) out(pe->declarationKindToken); lw.lineWriter.ensureSpace(); } + bool first = true; for (VariableDeclarationList *it = ast->declarations; it; it = it->next) { + if (!std::exchange(first, false)) { + out(","); + lw.lineWriter.ensureSpace(); + } accept(it->declaration); } } - out(";"); // ast->firstSemicolonToken + // We don't use writeOutSemicolon() here because we need a semicolon unconditionally. + // Repeats for the second semicolon token below. + out(u";"); // ast->firstSemicolonToken lw.lineWriter.ensureSpace(); accept(ast->condition); - out(";"); // ast->secondSemicolonToken + out(u";"); // ast->secondSemicolonToken lw.lineWriter.ensureSpace(); accept(ast->expression); out(ast->rparenToken); @@ -617,7 +626,7 @@ bool ScriptFormatter::visit(ContinueStatement *ast) out(ast->identifierToken); } if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -629,7 +638,7 @@ bool ScriptFormatter::visit(BreakStatement *ast) out(ast->identifierToken); } if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -642,7 +651,7 @@ bool ScriptFormatter::visit(ReturnStatement *ast) accept(ast->expression); } if (ast->returnToken.length > 0 && addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -667,7 +676,7 @@ bool ScriptFormatter::visit(ThrowStatement *ast) accept(ast->expression); } if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -860,7 +869,7 @@ bool ScriptFormatter::visit(StatementList *ast) for (StatementList *it = ast; it; it = it->next) { // ### work around parser bug: skip empty statements with wrong tokens if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) { - if (loc2Str(emptyStatement->semicolonToken) != QLatin1String(";")) + if (m_script->loc2Str(emptyStatement->semicolonToken) != QLatin1String(";")) continue; } @@ -935,7 +944,7 @@ bool ScriptFormatter::visit(ComputedPropertyName *) out("["); return true; } -bool ScriptFormatter::visit(Expression *el) +bool ScriptFormatter::visit(CommaExpression *el) { accept(el->left); out(","); @@ -946,7 +955,7 @@ bool ScriptFormatter::visit(Expression *el) bool ScriptFormatter::visit(ExpressionStatement *el) { if (addSemicolons()) - postOps[el->expression].append([this]() { out(";"); }); + postOps[el->expression].append([this, el]() { writeOutSemicolon(el); }); return true; } @@ -1107,16 +1116,16 @@ void ScriptFormatter::endVisit(ComputedPropertyName *) void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) { // add a semicolon at the end of the following expressions - // export * FromClause ; + // export * FromClause // export ExportClause FromClause ; if (ast->fromClause) { - out(";"); + writeOutSemicolon(ast); } // add a semicolon at the end of the following expressions // export ExportClause ; if (ast->exportClause && !ast->fromClause) { - out(";"); + writeOutSemicolon(ast); } // add a semicolon at the end of the following expressions @@ -1125,7 +1134,7 @@ void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) // lookahead ∉ { function, class } if (!(ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration || ast->variableStatementOrDeclaration->kind == Node::Kind_ClassDeclaration)) { - out(";"); + writeOutSemicolon(ast); } // ArrowFunction in QQmlJS::AST is handled with the help of FunctionDeclaration // and not as part of AssignmentExpression (as per ECMA @@ -1133,7 +1142,7 @@ void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) if (ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration && static_cast<AST::FunctionDeclaration *>(ast->variableStatementOrDeclaration) ->isArrowFunction) { - out(";"); + writeOutSemicolon(ast); } } } @@ -1154,9 +1163,9 @@ void ScriptFormatter::endVisit(AST::NamedImports *ast) out(ast->rightBraceToken); } -void ScriptFormatter::endVisit(AST::ImportDeclaration *) +void ScriptFormatter::endVisit(AST::ImportDeclaration *id) { - out(";"); + writeOutSemicolon(id); } void ScriptFormatter::throwRecursionDepthError() @@ -1164,14 +1173,84 @@ void ScriptFormatter::throwRecursionDepthError() out("/* ERROR: Hit recursion limit ScriptFormatter::visiting AST, rewrite failed */"); } -void reformatAst(OutWriter &lw, const std::shared_ptr<AstComments> &comments, - const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n) -{ - if (n) { - ScriptFormatter formatter(lw, comments, loc2Str, n); +// This is a set of characters that are not allowed to be at the beginning of a line +// after a semicolon for ASI. +using namespace Qt::StringLiterals; +static constexpr QLatin1StringView restrictedChars = "([/+-"_L1; + +// Given an existing semicolon, can we safely remove it without changing behavior +bool ScriptFormatter::canRemoveSemicolon(AST::Node *node) +{ + const auto canRelyOnASI = [this](Node *node) { + auto nodeLoc = node->lastSourceLocation().offset + 1; + auto code = m_script->engine()->code(); + // Bounds check for nodeLoc + if (qsizetype(nodeLoc) >= code.size()) + return false; + auto startIt = code.begin() + nodeLoc; + auto endIt = std::find_first_of(startIt, code.end(), restrictedChars.begin(), + restrictedChars.end()); + // No restricted character found, then it is safe to remove the semicolon + if (endIt == code.end()) + return true; + + // Check if there is at least one character between nodeLoc and the found character + // that are neither space chars nor semicolons. + bool hasOtherChars = + std::any_of(startIt, endIt, [](QChar ch) { return !(ch.isSpace() || ch == u';'); }); + + if (hasOtherChars) + return true; + + // Check if there is no linebreak between nodeLoc and the found character + return std::none_of(startIt, endIt, [](QChar c) { return c == u'\n'; }); + }; + + // 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";"); + lw.lineWriter.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 |