aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom/qqmldomreformatter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmldom/qqmldomreformatter.cpp')
-rw-r--r--src/qmldom/qqmldomreformatter.cpp135
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