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