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