Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmldomreformatter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QtQml/private/qqmljsast_p.h>
8#include <QtQml/private/qqmljsastvisitor_p.h>
9#include <QtQml/private/qqmljsengine_p.h>
10#include <QtQml/private/qqmljslexer_p.h>
11
12#include <QString>
13#include <algorithm>
14
15QT_BEGIN_NAMESPACE
16namespace QQmlJS {
17namespace Dom {
18
19using namespace AST;
20
21bool ScriptFormatter::preVisit(Node *n)
22{
23 const CommentedElement *c = comments->commentForNode(n, CommentAnchor{});
24 if (!c)
25 return true;
26
27 if (!c->preComments().empty()) {
28 deferredSpaces = 0;
29 for (const auto &preComment : c->preComments())
30 lw.maybeWriteComment(preComment);
31 }
32
33 postOps[n].append([c, this]() {
34 if (!c->postComments().empty()) {
35 deferredSpaces = 0;
36 for (const auto &postComment : c->postComments())
37 lw.maybeWriteComment(postComment);
38 }
39 });
40 return true;
41}
42
43void ScriptFormatter::postVisit(Node *n)
44{
45 for (auto &op : postOps[n]) {
46 op();
47 }
48 postOps.remove(n);
49}
50
51void ScriptFormatter::lnAcceptIndented(Node *node)
52{
53 int indent = lw.increaseIndent(1);
54 ensureNewline();
55 accept(node);
56 lw.decreaseIndent(1, indent);
57}
58
59bool ScriptFormatter::acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline)
60{
61 if (auto *es = cast<EmptyStatement *>(ast)) {
62 writeOutSemicolon(es);
63 return false;
64 }
65 if (cast<Block *>(ast)) {
66 ensureSpaceIfNoComment();
67 accept(ast);
68 if (finishWithSpaceOrNewline)
69 ensureSpaceIfNoComment();
70 return true;
71 } else {
72 if (finishWithSpaceOrNewline)
73 postOps[ast].append([this]() { ensureNewline(); });
74 lnAcceptIndented(ast);
75 return false;
76 }
77}
78
79bool ScriptFormatter::visit(ThisExpression *ast)
80{
81 out(ast->thisToken);
82 return true;
83}
84
85bool ScriptFormatter::visit(NullExpression *ast)
86{
87 out(ast->nullToken);
88 return true;
89}
90bool ScriptFormatter::visit(TrueLiteral *ast)
91{
92 out(ast->trueToken);
93 return true;
94}
95bool ScriptFormatter::visit(FalseLiteral *ast)
96{
97 out(ast->falseToken);
98 return true;
99}
100
101bool ScriptFormatter::visit(IdentifierExpression *ast)
102{
103 out(ast->identifierToken);
104 return true;
105}
106bool ScriptFormatter::visit(StringLiteral *ast)
107{
108 // correctly handle multiline literals
109 if (ast->literalToken.length == 0)
110 return true;
111 QStringView str = m_script->loc2Str(ast->literalToken);
112 if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
113 out(str.mid(0, 1));
114 lw.indentNextlines = false;
115 out(str.mid(1));
116 lw.indentNextlines = true;
117 } else {
118 out(str);
119 }
120 return true;
121}
122bool ScriptFormatter::visit(NumericLiteral *ast)
123{
124 outWithComments(ast->literalToken, ast);
125 return true;
126}
127bool ScriptFormatter::visit(RegExpLiteral *ast)
128{
129 out(ast->literalToken);
130 return true;
131}
132
133bool ScriptFormatter::visit(ArrayPattern *ast)
134{
135 out(ast->lbracketToken);
136 int baseIndent = lw.increaseIndent(1);
137 if (ast->elements) {
138 accept(ast->elements);
139 out(ast->commaToken);
140 auto lastElement = lastListElement(ast->elements);
141 if (lastElement->element && cast<ObjectPattern *>(lastElement->element->initializer)) {
142 ensureNewline();
143 }
144 } else {
145 out(ast->commaToken);
146 }
147 lw.decreaseIndent(1, baseIndent);
148 out(ast->rbracketToken);
149 return false;
150}
151
152bool ScriptFormatter::visit(ObjectPattern *ast)
153{
154 out(ast->lbraceToken);
155 ++expressionDepth;
156 if (ast->properties) {
157 lnAcceptIndented(ast->properties);
158 ensureNewline();
159 }
160 --expressionDepth;
161 out(ast->rbraceToken);
162 return false;
163}
164
165bool ScriptFormatter::visit(PatternElementList *ast)
166{
167 for (PatternElementList *it = ast; it; it = it->next) {
168 const bool isObjectInitializer =
169 it->element && cast<ObjectPattern *>(it->element->initializer);
170 if (isObjectInitializer)
171 ensureNewline();
172
173 if (it->elision)
174 accept(it->elision);
175 if (it->elision && it->element) {
176 outWithComments(it->elision->commaToken, it);
177 ensureSpaceIfNoComment();
178 }
179 if (it->element)
180 accept(it->element);
181 if (it->next) {
182 outWithComments(it->next->commaToken, it);
183 ensureSpaceIfNoComment();
184 if (isObjectInitializer)
185 ensureNewline();
186 }
187 }
188 return false;
189}
190
191bool ScriptFormatter::visit(PatternPropertyList *ast)
192{
193 for (PatternPropertyList *it = ast; it; it = it->next) {
194 accept(it->property);
195 if (it->next) {
196 out(",");
197 ensureNewline();
198 }
199 }
200 return false;
201}
202
203// https://262.ecma-international.org/7.0/#prod-PropertyDefinition
204bool ScriptFormatter::visit(AST::PatternProperty *property)
205{
206 if (property->type == PatternElement::Getter || property->type == PatternElement::Setter
207 || property->type == PatternElement::Method) {
208 // note that MethodDefinitions and FunctionDeclarations have different syntax
209 // https://262.ecma-international.org/7.0/#prod-MethodDefinition
210 // https://262.ecma-international.org/7.0/#prod-FunctionDeclaration
211 // hence visit(FunctionDeclaration*) is not quite appropriate here
212 if (property->type == PatternProperty::Getter) {
213 out("get");
214 ensureSpaceIfNoComment();
215 } else if (property->type == PatternProperty::Setter) {
216 out("set");
217 ensureSpaceIfNoComment();
218 }
219 FunctionExpression *f = AST::cast<FunctionExpression *>(property->initializer);
220 if (f->isGenerator) {
221 out("*");
222 }
223 accept(property->name);
224 out(f->lparenToken);
225 accept(f->formals);
226 out(f->rparenToken);
227 ensureSpaceIfNoComment();
228 out(f->lbraceToken);
229 const bool scoped = f->lbraceToken.isValid();
230 if (scoped)
231 ++expressionDepth;
232 if (f->body) {
233 if (f->body->next || scoped) {
234 lnAcceptIndented(f->body);
235 lw.newline();
236 } else {
237 auto baseIndent = lw.increaseIndent(1);
238 accept(f->body);
239 lw.decreaseIndent(1, baseIndent);
240 }
241 }
242 if (scoped)
243 --expressionDepth;
244 out(f->rbraceToken);
245 return false;
246 }
247
248 // IdentifierReference[?Yield]
249 accept(property->name);
250 bool useInitializer = false;
251 const bool bindingIdentifierExist = !property->bindingIdentifier.isEmpty();
252 if (property->colonToken.isValid()) {
253 // PropertyName[?Yield] : AssignmentExpression[In, ?Yield]
254 out(":");
255 ensureSpaceIfNoComment();
256 useInitializer = true;
257 if (bindingIdentifierExist)
258 out(property->bindingIdentifier);
259 if (property->bindingTarget)
260 accept(property->bindingTarget);
261 }
262
263 if (property->initializer) {
264 // CoverInitializedName[?Yield]
265 if (bindingIdentifierExist) {
266 ensureSpaceIfNoComment();
267 out("=");
268 ensureSpaceIfNoComment();
269 useInitializer = true;
270 }
271 if (useInitializer)
272 accept(property->initializer);
273 }
274 return false;
275}
276
277bool ScriptFormatter::visit(NestedExpression *ast)
278{
279 out(ast->lparenToken);
280 int baseIndent = lw.increaseIndent(1);
281 accept(ast->expression);
282 lw.decreaseIndent(1, baseIndent);
283 out(ast->rparenToken);
284 return false;
285}
286
287bool ScriptFormatter::visit(IdentifierPropertyName *ast)
288{
289 out(ast->id.toString());
290 return true;
291}
292bool ScriptFormatter::visit(StringLiteralPropertyName *ast)
293{
294 out(ast->propertyNameToken);
295 return true;
296}
297bool ScriptFormatter::visit(NumericLiteralPropertyName *ast)
298{
299 out(QString::number(ast->id));
300 return true;
301}
302
303bool ScriptFormatter::visit(TemplateLiteral *ast)
304{
305 // correctly handle multiline literals
306 if (ast->literalToken.length != 0) {
307 QStringView str = m_script->loc2Str(ast->literalToken);
308 if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
309 out(str.mid(0, 1));
310 lw.indentNextlines = false;
311 out(str.mid(1));
312 lw.indentNextlines = true;
313 } else {
314 out(str);
315 }
316 }
317 accept(ast->expression);
318 return true;
319}
320
321bool ScriptFormatter::visit(ArrayMemberExpression *ast)
322{
323 accept(ast->base);
324 out(ast->optionalToken);
325 out(ast->lbracketToken);
326 int indent = lw.increaseIndent(1);
327 accept(ast->expression);
328 lw.decreaseIndent(1, indent);
329 out(ast->rbracketToken);
330 return false;
331}
332
333bool ScriptFormatter::visit(FieldMemberExpression *ast)
334{
335 accept(ast->base);
336 out(ast->dotToken);
337 out(ast->identifierToken);
338 return false;
339}
340
341bool ScriptFormatter::visit(NewMemberExpression *ast)
342{
343 out("new"); // ast->newToken
344 ensureSpaceIfNoComment();
345 accept(ast->base);
346 out(ast->lparenToken);
347 accept(ast->arguments);
348 out(ast->rparenToken);
349 return false;
350}
351
352bool ScriptFormatter::visit(NewExpression *ast)
353{
354 out("new"); // ast->newToken
355 ensureSpaceIfNoComment();
356 accept(ast->expression);
357 return false;
358}
359
360bool ScriptFormatter::visit(CallExpression *ast)
361{
362 accept(ast->base);
363 out(ast->optionalToken);
364 out(ast->lparenToken);
365 accept(ast->arguments);
366 out(ast->rparenToken);
367 return false;
368}
369
370bool ScriptFormatter::visit(PostIncrementExpression *ast)
371{
372 accept(ast->base);
373 out(ast->incrementToken);
374 return false;
375}
376
377bool ScriptFormatter::visit(PostDecrementExpression *ast)
378{
379 accept(ast->base);
380 out(ast->decrementToken);
381 return false;
382}
383
384bool ScriptFormatter::visit(PreIncrementExpression *ast)
385{
386 out(ast->incrementToken);
387 accept(ast->expression);
388 return false;
389}
390
391bool ScriptFormatter::visit(PreDecrementExpression *ast)
392{
393 out(ast->decrementToken);
394 accept(ast->expression);
395 return false;
396}
397
398bool ScriptFormatter::visit(DeleteExpression *ast)
399{
400 out("delete"); // ast->deleteToken
401 ensureSpaceIfNoComment();
402 accept(ast->expression);
403 return false;
404}
405
406bool ScriptFormatter::visit(VoidExpression *ast)
407{
408 out("void"); // ast->voidToken
409 ensureSpaceIfNoComment();
410 accept(ast->expression);
411 return false;
412}
413
414bool ScriptFormatter::visit(TypeOfExpression *ast)
415{
416 out("typeof"); // ast->typeofToken
417 ensureSpaceIfNoComment();
418 accept(ast->expression);
419 return false;
420}
421
422bool ScriptFormatter::visit(UnaryPlusExpression *ast)
423{
424 out(ast->plusToken);
425 accept(ast->expression);
426 return false;
427}
428
429bool ScriptFormatter::visit(UnaryMinusExpression *ast)
430{
431 out(ast->minusToken);
432 accept(ast->expression);
433 return false;
434}
435
436bool ScriptFormatter::visit(TildeExpression *ast)
437{
438 out(ast->tildeToken);
439 accept(ast->expression);
440 return false;
441}
442
443bool ScriptFormatter::visit(NotExpression *ast)
444{
445 out(ast->notToken);
446 accept(ast->expression);
447 return false;
448}
449
450bool ScriptFormatter::visit(BinaryExpression *ast)
451{
452 accept(ast->left);
453 ensureSpaceIfNoComment();
454 out(ast->operatorToken);
455 ensureSpaceIfNoComment();
456 accept(ast->right);
457 return false;
458}
459
460bool ScriptFormatter::visit(ConditionalExpression *ast)
461{
462 accept(ast->expression);
463 ensureSpaceIfNoComment();
464 out("?"); // ast->questionToken
465 ensureSpaceIfNoComment();
466 accept(ast->ok);
467 ensureSpaceIfNoComment();
468 out(":"); // ast->colonToken
469 ensureSpaceIfNoComment();
470 accept(ast->ko);
471 return false;
472}
473
474bool ScriptFormatter::visit(Block *ast)
475{
476 // write comments manually because we need to indent before writing a potential post comment
477 const CommentedElement *c =
478 comments->commentForNode(ast, CommentAnchor::from(ast->lbraceToken));
479 if (c)
480 writePreComment(c);
481 out(ast->lbraceToken);
482 const int indent = lw.increaseIndent();
483 if (c)
484 writePostComment(c);
485
486 if (ast->statements) {
487 ++expressionDepth;
488 ensureNewline();
489 accept(ast->statements);
490 ensureNewline();
491 --expressionDepth;
492 }
493
494 // write comments manually because we need to write a potential pre-comment before decreasing
495 // the indentation
496 c = comments->commentForNode(ast, CommentAnchor::from(ast->rbraceToken));
497 if (c)
498 writePreComment(c);
499 lw.decreaseIndent(1, indent);
500 out(ast->rbraceToken);
501 if (c)
502 writePostComment(c);
503 return false;
504}
505
506bool ScriptFormatter::visit(VariableStatement *ast)
507{
508 out(ast->declarationKindToken);
509 ensureSpaceIfNoComment();
510 accept(ast->declarations);
511 if (addSemicolons())
512 writeOutSemicolon(ast);
513 return false;
514}
515
516bool ScriptFormatter::visit(PatternElement *ast)
517{
518 switch (ast->type) {
519 case PatternElement::Literal:
520 case PatternElement::Method:
521 case PatternElement::Binding:
522 break;
523 case PatternElement::Getter:
524 out("get");
525 ensureSpaceIfNoComment();
526 break;
527 case PatternElement::Setter:
528 out("set");
529 ensureSpaceIfNoComment();
530 break;
531 case PatternElement::SpreadElement:
532 out("...");
533 break;
534 }
535
536 accept(ast->bindingTarget);
537 if (!ast->destructuringPattern())
538 out(ast->identifierToken);
539 if (ast->initializer) {
540 if (ast->isVariableDeclaration() || ast->type == AST::PatternElement::Binding) {
541 ensureSpaceIfNoComment();
542 outWithComments(ast->equalToken, ast);
543 ensureSpaceIfNoComment();
544 }
545 accept(ast->initializer);
546 }
547 accept(ast->typeAnnotation);
548 return false;
549}
550
551bool ScriptFormatter::visit(TypeAnnotation *ast)
552{
553 out(ast->colonToken);
554 ensureSpaceIfNoComment();
555 accept(ast->type);
556 return false;
557}
558
559bool ScriptFormatter::visit(Type *ast)
560{
561 accept(ast->typeId);
562 if (ast->typeArgument) {
563 outWithComments(ast->lAngleBracketToken, ast);
564 accept(ast->typeArgument);
565 outWithComments(ast->rAngleBracketToken, ast);
566 }
567 return false;
568}
569
570bool ScriptFormatter::visit(UiQualifiedId *ast)
571{
572 for (UiQualifiedId *it = ast; it; it = it->next) {
573 outWithComments(it->dotToken, it);
574 outWithComments(it->identifierToken, it);
575 }
576 return false;
577}
578
579bool ScriptFormatter::visit(EmptyStatement *)
580{
581 lw.lineWriter.ensureSemicolon();
582 return false;
583}
584
585bool ScriptFormatter::visit(IfStatement *ast)
586{
587 out(ast->ifToken);
588 ensureSpaceIfNoComment();
589 out(ast->lparenToken);
590 preVisit(ast->expression);
591 ast->expression->accept0(this);
592 out(ast->rparenToken);
593 postVisit(ast->expression);
594 acceptBlockOrIndented(ast->ok, ast->ko);
595 if (ast->ko) {
596 out(ast->elseToken);
597 if (cast<Block *>(ast->ko) || cast<IfStatement *>(ast->ko)) {
598 ensureSpaceIfNoComment();
599 accept(ast->ko);
600 } else {
601 lnAcceptIndented(ast->ko);
602 }
603 }
604 return false;
605}
606
607bool ScriptFormatter::visit(DoWhileStatement *ast)
608{
609 out(ast->doToken);
610 acceptBlockOrIndented(ast->statement, true);
611 out(ast->whileToken);
612 ensureSpaceIfNoComment();
613 outWithComments(ast->lparenToken, ast);
614 accept(ast->expression);
615 outWithComments(ast->rparenToken, ast);
616 return false;
617}
618
619bool ScriptFormatter::visit(WhileStatement *ast)
620{
621 out(ast->whileToken);
622 ensureSpaceIfNoComment();
623 outWithComments(ast->lparenToken, ast);
624 accept(ast->expression);
625 outWithComments(ast->rparenToken, ast);
626 acceptBlockOrIndented(ast->statement);
627 return false;
628}
629
630bool ScriptFormatter::visit(ForStatement *ast)
631{
632 out(ast->forToken);
633 ensureSpaceIfNoComment();
634 outWithComments(ast->lparenToken, ast);
635 if (ast->initialiser) {
636 accept(ast->initialiser);
637 } else if (ast->declarations) {
638 if (auto pe = ast->declarations->declaration) {
639 out(pe->declarationKindToken);
640 ensureSpaceIfNoComment();
641 }
642 bool first = true;
643 for (VariableDeclarationList *it = ast->declarations; it; it = it->next) {
644 if (!std::exchange(first, false)) {
645 out(",");
646 ensureSpaceIfNoComment();
647 }
648 accept(it->declaration);
649 }
650 }
651 // We don't use writeOutSemicolon() here because we need a semicolon unconditionally.
652 // Repeats for the second semicolon token below.
653 out(u";"); // ast->firstSemicolonToken
654 ensureSpaceIfNoComment();
655 accept(ast->condition);
656 out(u";"); // ast->secondSemicolonToken
657 ensureSpaceIfNoComment();
658 accept(ast->expression);
659 outWithComments(ast->rparenToken, ast);
660 acceptBlockOrIndented(ast->statement);
661 return false;
662}
663
664bool ScriptFormatter::visit(ForEachStatement *ast)
665{
666 out(ast->forToken);
667 ensureSpaceIfNoComment();
668 out(ast->lparenToken);
669 if (auto pe = AST::cast<PatternElement *>(ast->lhs)) {
670 out(pe->declarationKindToken);
671 ensureSpaceIfNoComment();
672 }
673 accept(ast->lhs);
674 ensureSpaceIfNoComment();
675 out(ast->inOfToken);
676 ensureSpaceIfNoComment();
677 accept(ast->expression);
678 out(ast->rparenToken);
679 acceptBlockOrIndented(ast->statement);
680 return false;
681}
682
683bool ScriptFormatter::visit(ContinueStatement *ast)
684{
685 out(ast->continueToken);
686 if (!ast->label.isNull()) {
687 ensureSpaceIfNoComment();
688 out(ast->identifierToken);
689 }
690 if (addSemicolons())
691 writeOutSemicolon(ast);
692 return false;
693}
694
695bool ScriptFormatter::visit(BreakStatement *ast)
696{
697 out(ast->breakToken);
698 if (!ast->label.isNull()) {
699 ensureSpaceIfNoComment();
700 out(ast->identifierToken);
701 }
702 if (addSemicolons())
703 writeOutSemicolon(ast);
704 return false;
705}
706
707bool ScriptFormatter::visit(ReturnStatement *ast)
708{
709 out(ast->returnToken);
710 if (ast->expression) {
711 if (ast->returnToken.length != 0)
712 ensureSpaceIfNoComment();
713 accept(ast->expression);
714 }
715 if (ast->returnToken.length > 0 && addSemicolons())
716 writeOutSemicolon(ast);
717 return false;
718}
719
720bool ScriptFormatter::visit(YieldExpression *ast)
721{
722 out(ast->yieldToken);
723 if (ast->isYieldStar)
724 out("*");
725 if (ast->expression) {
726 if (ast->yieldToken.isValid())
727 ensureSpaceIfNoComment();
728 accept(ast->expression);
729 }
730 return false;
731}
732
733bool ScriptFormatter::visit(ThrowStatement *ast)
734{
735 out(ast->throwToken);
736 if (ast->expression) {
737 ensureSpaceIfNoComment();
738 accept(ast->expression);
739 }
740 if (addSemicolons())
741 writeOutSemicolon(ast);
742 return false;
743}
744
745bool ScriptFormatter::visit(WithStatement *ast)
746{
747 out(ast->withToken);
748 ensureSpaceIfNoComment();
749 out(ast->lparenToken);
750 accept(ast->expression);
751 out(ast->rparenToken);
752 acceptBlockOrIndented(ast->statement);
753 return false;
754}
755
756bool ScriptFormatter::visit(SwitchStatement *ast)
757{
758 out(ast->switchToken);
759 ensureSpaceIfNoComment();
760 out(ast->lparenToken);
761 accept(ast->expression);
762 out(ast->rparenToken);
763 ensureSpaceIfNoComment();
764 accept(ast->block);
765 return false;
766}
767
768bool ScriptFormatter::visit(CaseBlock *ast)
769{
770 out(ast->lbraceToken);
771 ++expressionDepth;
772 ensureNewline();
773 accept(ast->clauses);
774 if (ast->clauses && ast->defaultClause)
775 ensureNewline();
776 accept(ast->defaultClause);
777 if (ast->moreClauses)
778 ensureNewline();
779 accept(ast->moreClauses);
780 ensureNewline();
781 --expressionDepth;
782 out(ast->rbraceToken);
783 return false;
784}
785
786bool ScriptFormatter::visit(CaseClause *ast)
787{
788 out("case"); // ast->caseToken
789 ensureSpaceIfNoComment();
790 accept(ast->expression);
791 outWithComments(ast->colonToken, ast);
792 if (ast->statements)
793 lnAcceptIndented(ast->statements);
794 return false;
795}
796
797bool ScriptFormatter::visit(DefaultClause *ast)
798{
799 out(ast->defaultToken);
800 out(ast->colonToken);
801 lnAcceptIndented(ast->statements);
802 return false;
803}
804
805bool ScriptFormatter::visit(LabelledStatement *ast)
806{
807 out(ast->identifierToken);
808 out(":"); // ast->colonToken
809 ensureSpaceIfNoComment();
810 accept(ast->statement);
811 return false;
812}
813
814bool ScriptFormatter::visit(TryStatement *ast)
815{
816 out("try"); // ast->tryToken
817 ensureSpaceIfNoComment();
818 accept(ast->statement);
819 if (ast->catchExpression) {
820 ensureSpaceIfNoComment();
821 accept(ast->catchExpression);
822 }
823 if (ast->finallyExpression) {
824 ensureSpaceIfNoComment();
825 accept(ast->finallyExpression);
826 }
827 return false;
828}
829
830bool ScriptFormatter::visit(Catch *ast)
831{
832 out(ast->catchToken);
833 ensureSpaceIfNoComment();
834 out(ast->lparenToken);
835 out(ast->identifierToken);
836 out(")"); // ast->rparenToken
837 ensureSpaceIfNoComment();
838 accept(ast->statement);
839 return false;
840}
841
842bool ScriptFormatter::visit(Finally *ast)
843{
844 out("finally"); // ast->finallyToken
845 ensureSpaceIfNoComment();
846 accept(ast->statement);
847 return false;
848}
849
850bool ScriptFormatter::visit(FunctionDeclaration *ast)
851{
852 return ScriptFormatter::visit(static_cast<FunctionExpression *>(ast));
853}
854
855bool ScriptFormatter::visit(FunctionExpression *ast)
856{
857 if (!ast->isArrowFunction) {
858 if (ast->isGenerator) {
859 out("function*");
860 ensureSpaceIfNoComment();
861 } else {
862 out("function");
863 ensureSpaceIfNoComment();
864 }
865 outWithComments(ast->identifierToken, ast);
866 }
867
868 const bool removeParentheses = ast->isArrowFunction && ast->formals && !ast->formals->next
869 && (ast->formals->element && !ast->formals->element->bindingTarget);
870
871 // note: qmlformat removes the parentheses for "(x) => x". In that case, we still need
872 // to print potential comments attached to `(` or `)` via `OnlyComments` option.
873 outWithComments(ast->lparenToken, ast, removeParentheses ? OnlyComments : TokenAndComment);
874 int baseIndent = lw.increaseIndent(1);
875 accept(ast->formals);
876 lw.decreaseIndent(1, baseIndent);
877 outWithComments(ast->rparenToken, ast, removeParentheses ? OnlyComments : TokenAndComment);
878 accept(ast->typeAnnotation);
879 ensureSpaceIfNoComment();
880 if (ast->isArrowFunction) {
881 out("=>");
882 ensureSpaceIfNoComment();
883 }
884 outWithComments(ast->lbraceToken, ast);
885 if (ast->lbraceToken.length != 0)
886 ++expressionDepth;
887 if (ast->body) {
888 if (ast->body->next || ast->lbraceToken.length != 0) {
889 lnAcceptIndented(ast->body);
890 ensureNewline();
891 } else {
892 // print a single statement in one line. E.g. x => x * 2
893 baseIndent = lw.increaseIndent(1);
894 accept(ast->body);
895 lw.decreaseIndent(1, baseIndent);
896 }
897 }
898 if (ast->lbraceToken.length != 0)
899 --expressionDepth;
900 outWithComments(ast->rbraceToken, ast);
901 return false;
902}
903
904bool ScriptFormatter::visit(Elision *ast)
905{
906 for (Elision *it = ast; it; it = it->next) {
907 if (it->next) {
908 out(","); // ast->commaToken
909 ensureSpaceIfNoComment();
910 }
911 }
912 return false;
913}
914
915bool ScriptFormatter::visit(ArgumentList *ast)
916{
917 for (ArgumentList *it = ast; it; it = it->next) {
918 if (it->isSpreadElement)
919 out("...");
920 accept(it->expression);
921 if (it->next) {
922 out(","); // it->commaToken
923 ensureSpaceIfNoComment();
924 }
925 }
926 return false;
927}
928
929bool ScriptFormatter::visit(StatementList *ast)
930{
931 ++expressionDepth;
932 for (StatementList *it = ast; it; it = it->next) {
933 // ### work around parser bug: skip empty statements with wrong tokens
934 if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) {
935 if (m_script->loc2Str(emptyStatement->semicolonToken) != QLatin1String(";"))
936 continue;
937 }
938
939 accept(it->statement);
940 if (it->next) {
941 // There might be a post-comment attached to the current
942 // statement or a pre-comment attached to the next
943 // statmente or both.
944 // If any of those are present they will take care of
945 // handling the spacing between the statements so we
946 // don't need to push any newline.
947 auto *commentForCurrentStatement =
948 comments->commentForNode(it->statement, CommentAnchor{});
949 auto *commentForNextStatement =
950 comments->commentForNode(it->next->statement, CommentAnchor{});
951
952 if (
953 (commentForCurrentStatement && !commentForCurrentStatement->postComments().empty())
954 || (commentForNextStatement && !commentForNextStatement->preComments().empty())
955 ) continue;
956
957 quint32 lineDelta = it->next->firstSourceLocation().startLine
958 - it->statement->lastSourceLocation().startLine;
959 lineDelta = std::clamp(lineDelta, quint32{ 1 }, quint32{ 2 });
960
961 ensureNewline(lineDelta);
962 }
963 }
964 --expressionDepth;
965 return false;
966}
967
968bool ScriptFormatter::visit(VariableDeclarationList *ast)
969{
970 for (VariableDeclarationList *it = ast; it; it = it->next) {
971 accept(it->declaration);
972 if (it->next) {
973 out(","); // it->commaToken
974 ensureSpaceIfNoComment();
975 }
976 }
977 return false;
978}
979
980bool ScriptFormatter::visit(CaseClauses *ast)
981{
982 for (CaseClauses *it = ast; it; it = it->next) {
983 accept(it->clause);
984 if (it->next)
985 ensureNewline();
986 }
987 return false;
988}
989
990bool ScriptFormatter::visit(FormalParameterList *ast)
991{
992 for (FormalParameterList *it = ast; it; it = it->next) {
993 accept(it->element);
994 if (it->commaToken.isValid()) {
995 outWithComments(it->commaToken, it);
996 ensureSpaceIfNoComment();
997 }
998 }
999 return false;
1000}
1001
1002// to check
1003bool ScriptFormatter::visit(SuperLiteral *)
1004{
1005 out("super");
1006 return true;
1007}
1008bool ScriptFormatter::visit(ComputedPropertyName *)
1009{
1010 out("[");
1011 return true;
1012}
1013bool ScriptFormatter::visit(CommaExpression *el)
1014{
1015 accept(el->left);
1016 out(",");
1017 ensureSpaceIfNoComment();
1018 accept(el->right);
1019 return false;
1020}
1021bool ScriptFormatter::visit(ExpressionStatement *el)
1022{
1023 if (addSemicolons())
1024 postOps[el->expression].append([this, el]() { writeOutSemicolon(el); });
1025 return true;
1026}
1027
1028// Return false because we want to omit default function calls in accept0 implementation.
1029bool ScriptFormatter::visit(ClassDeclaration *ast)
1030{
1031 out(ast->classToken);
1032 ensureSpaceIfNoComment();
1033 outWithComments(ast->identifierToken, ast);
1034 if (ast->heritage) {
1035 ensureSpaceIfNoComment();
1036 out("extends");
1037 ensureSpaceIfNoComment();
1038 accept(ast->heritage);
1039 }
1040 ensureSpaceIfNoComment();
1041 outWithComments(ast->lbraceToken, ast);
1042 int baseIndent = lw.increaseIndent();
1043 for (ClassElementList *it = ast->elements; it; it = it->next) {
1044 lw.newline();
1045 if (it->isStatic) {
1046 out("static");
1047 ensureSpaceIfNoComment();
1048 }
1049 accept(it->property);
1050 lw.newline();
1051 }
1052 lw.decreaseIndent(1, baseIndent);
1053 outWithComments(ast->rbraceToken, ast);
1054 return false;
1055}
1056
1057bool ScriptFormatter::visit(AST::ImportDeclaration *ast)
1058{
1059 out(ast->importToken);
1060 ensureSpaceIfNoComment();
1061 if (!ast->moduleSpecifier.isNull()) {
1062 out(ast->moduleSpecifierToken);
1063 }
1064 return true;
1065}
1066
1067bool ScriptFormatter::visit(AST::ImportSpecifier *ast)
1068{
1069 if (!ast->identifier.isNull()) {
1070 out(ast->identifierToken);
1071 ensureSpaceIfNoComment();
1072 out("as");
1073 ensureSpaceIfNoComment();
1074 }
1075 out(ast->importedBindingToken);
1076 return true;
1077}
1078
1079bool ScriptFormatter::visit(AST::NameSpaceImport *ast)
1080{
1081 out(ast->starToken);
1082 ensureSpaceIfNoComment();
1083 out("as");
1084 ensureSpaceIfNoComment();
1085 out(ast->importedBindingToken);
1086 return true;
1087}
1088
1089bool ScriptFormatter::visit(AST::ImportsList *ast)
1090{
1091 for (ImportsList *it = ast; it; it = it->next) {
1092 accept(it->importSpecifier);
1093 if (it->next) {
1094 out(",");
1095 ensureSpaceIfNoComment();
1096 }
1097 }
1098 return false;
1099}
1100bool ScriptFormatter::visit(AST::NamedImports *ast)
1101{
1102 out(ast->leftBraceToken);
1103 if (ast->importsList) {
1104 ensureSpaceIfNoComment();
1105 }
1106 return true;
1107}
1108
1109bool ScriptFormatter::visit(AST::ImportClause *ast)
1110{
1111 if (!ast->importedDefaultBinding.isNull()) {
1112 out(ast->importedDefaultBindingToken);
1113 if (ast->nameSpaceImport || ast->namedImports) {
1114 out(",");
1115 ensureSpaceIfNoComment();
1116 }
1117 }
1118 return true;
1119}
1120
1121bool ScriptFormatter::visit(AST::ExportDeclaration *ast)
1122{
1123 out(ast->exportToken);
1124 ensureSpaceIfNoComment();
1125 if (ast->exportDefault) {
1126 out("default");
1127 ensureSpaceIfNoComment();
1128 }
1129 if (ast->exportsAll()) {
1130 out("*");
1131 }
1132 return true;
1133}
1134
1135bool ScriptFormatter::visit(AST::ExportClause *ast)
1136{
1137 out(ast->leftBraceToken);
1138 if (ast->exportsList) {
1139 ensureSpaceIfNoComment();
1140 }
1141 return true;
1142}
1143
1144bool ScriptFormatter::visit(AST::ExportSpecifier *ast)
1145{
1146 out(ast->identifier);
1147 if (ast->exportedIdentifierToken.isValid()) {
1148 ensureSpaceIfNoComment();
1149 out("as");
1150 ensureSpaceIfNoComment();
1151 out(ast->exportedIdentifier);
1152 }
1153 return true;
1154}
1155
1156bool ScriptFormatter::visit(AST::ExportsList *ast)
1157{
1158 for (ExportsList *it = ast; it; it = it->next) {
1159 accept(it->exportSpecifier);
1160 if (it->next) {
1161 out(",");
1162 ensureSpaceIfNoComment();
1163 }
1164 }
1165 return false;
1166}
1167
1168bool ScriptFormatter::visit(AST::FromClause *ast)
1169{
1170 ensureSpaceIfNoComment();
1171 out(ast->fromToken);
1172 ensureSpaceIfNoComment();
1173 out(ast->moduleSpecifierToken);
1174 return true;
1175}
1176
1177void ScriptFormatter::endVisit(ComputedPropertyName *)
1178{
1179 out("]");
1180}
1181
1182void ScriptFormatter::endVisit(AST::ExportDeclaration *ast)
1183{
1184 // add a semicolon at the end of the following expressions
1185 // export * FromClause
1186 // export ExportClause FromClause ;
1187 if (ast->fromClause) {
1188 writeOutSemicolon(ast);
1189 }
1190
1191 // add a semicolon at the end of the following expressions
1192 // export ExportClause ;
1193 if (ast->exportClause && !ast->fromClause) {
1194 writeOutSemicolon(ast);
1195 }
1196
1197 // add a semicolon at the end of the following expressions
1198 // export default [lookahead ∉ { function, class }] AssignmentExpression;
1199 if (ast->exportDefault && ast->variableStatementOrDeclaration) {
1200 // lookahead ∉ { function, class }
1201 if (!(ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration
1202 || ast->variableStatementOrDeclaration->kind == Node::Kind_ClassDeclaration)) {
1203 writeOutSemicolon(ast);
1204 }
1205 // ArrowFunction in QQmlJS::AST is handled with the help of FunctionDeclaration
1206 // and not as part of AssignmentExpression (as per ECMA
1207 // https://262.ecma-international.org/7.0/#prod-AssignmentExpression)
1208 if (ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration
1209 && static_cast<AST::FunctionDeclaration *>(ast->variableStatementOrDeclaration)
1210 ->isArrowFunction) {
1211 writeOutSemicolon(ast);
1212 }
1213 }
1214}
1215
1216void ScriptFormatter::endVisit(AST::ExportClause *ast)
1217{
1218 if (ast->exportsList) {
1219 ensureSpaceIfNoComment();
1220 }
1221 out(ast->rightBraceToken);
1222}
1223
1224void ScriptFormatter::endVisit(AST::NamedImports *ast)
1225{
1226 if (ast->importsList) {
1227 ensureSpaceIfNoComment();
1228 }
1229 out(ast->rightBraceToken);
1230}
1231
1232void ScriptFormatter::endVisit(AST::ImportDeclaration *id)
1233{
1234 writeOutSemicolon(id);
1235}
1236
1237void ScriptFormatter::throwRecursionDepthError()
1238{
1239 out("/* ERROR: Hit recursion limit ScriptFormatter::visiting AST, rewrite failed */");
1240}
1241
1242// This is a set of characters that are not allowed to be at the beginning of a line
1243// after a semicolon for ASI.
1244
1245static constexpr QStringView restrictedChars = u"([/+-";
1246
1247// Given an existing semicolon, can we safely remove it without changing behavior
1248bool ScriptFormatter::canRemoveSemicolon(AST::Node *node)
1249{
1250 const auto canRelyOnASI = [this](Node *node) {
1251 auto nodeLoc = node->lastSourceLocation().offset + 1;
1252 auto code = m_script->engine()->code();
1253 // Bounds check for nodeLoc
1254 if (qsizetype(nodeLoc) >= code.size())
1255 return false;
1256 auto startIt = code.begin() + nodeLoc;
1257 auto endIt = std::find_first_of(startIt, code.end(), restrictedChars.begin(),
1258 restrictedChars.end());
1259 // No restricted character found, then it is safe to remove the semicolon
1260 if (endIt == code.end())
1261 return true;
1262
1263 // Check if there is at least one character between nodeLoc and the found character
1264 // that are neither space chars nor semicolons.
1265 bool hasOtherChars =
1266 std::any_of(startIt, endIt, [](QChar ch) { return !(ch.isSpace() || ch == u';'); });
1267
1268 if (hasOtherChars)
1269 return true;
1270
1271 // Check if there is no linebreak between nodeLoc and the found character
1272 return std::none_of(startIt, endIt, [](QChar c) { return c == u'\n'; });
1273 };
1274
1275 // Check if the node is a statement that requires a semicolon to avoid ASI issues
1276 switch (node->kind) {
1277 case AST::Node::Kind_ExpressionStatement:
1278 return canRelyOnASI(cast<ExpressionStatement *>(node));
1279 case AST::Node::Kind_VariableStatement:
1280 return canRelyOnASI(cast<VariableStatement *>(node));
1281 case AST::Node::Kind_EmptyStatement:
1282 return false;
1283 case AST::Node::Kind_ContinueStatement:
1284 case AST::Node::Kind_BreakStatement:
1285 case AST::Node::Kind_ReturnStatement:
1286 case AST::Node::Kind_ThrowStatement:
1287 case AST::Node::Kind_ExportDeclaration:
1288 case AST::Node::Kind_ImportDeclaration:
1289 case AST::Node::Kind_FromClause:
1290 case AST::Node::Kind_ExportClause:
1291 default:
1292 return true;
1293 }
1294}
1295
1296OutWriter &ScriptFormatter::writeOutSemicolon(AST::Node *node)
1297{
1298 if (!node)
1299 return lw;
1300 switch (lw.lineWriter.options().semicolonRule) {
1301 case LineWriterOptions::SemicolonRule::Essential:
1302 if (!canRemoveSemicolon(node))
1303 out(u";");
1304 ensureNewline();
1305 return lw;
1306 case LineWriterOptions::SemicolonRule::Always:
1307 out(u";");
1308 return lw;
1309 default:
1310 Q_UNREACHABLE_RETURN(lw);
1311 }
1312}
1313
1314void reformatAst(OutWriter &lw, const QQmlJS::Dom::ScriptExpression *const script)
1315{
1316 if (script)
1317 ScriptFormatter formatter(lw, script);
1318}
1319
1320} // namespace Dom
1321} // namespace QQmlJS
1322QT_END_NAMESPACE