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

source code of qtdeclarative/src/qmldom/qqmldomreformatter.cpp