1// Copyright (C) 2022 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 "qqmldomcodeformatter_p.h"
5
6#include <QLoggingCategory>
7#include <QMetaEnum>
8
9static Q_LOGGING_CATEGORY(formatterLog, "qt.qmldom.formatter", QtWarningMsg);
10
11QT_BEGIN_NAMESPACE
12namespace QQmlJS {
13namespace Dom {
14
15using StateType = FormatTextStatus::StateType;
16using State = FormatTextStatus::State;
17
18State FormatTextStatus::state(int belowTop) const
19{
20 if (belowTop < states.size())
21 return states.at(i: states.size() - 1 - belowTop);
22 else
23 return State();
24}
25
26QString FormatTextStatus::stateToString(StateType type)
27{
28 const QMetaEnum &metaEnum =
29 staticMetaObject.enumerator(index: staticMetaObject.indexOfEnumerator(name: "StateType"));
30 return QString::fromUtf8(utf8: metaEnum.valueToKey(value: int(type)));
31}
32
33void FormatPartialStatus::enterState(StateType newState)
34{
35 int savedIndentDepth = currentIndent;
36 defaultOnEnter(newState, indentDepth: &currentIndent, savedIndentDepth: &savedIndentDepth);
37 currentStatus.pushState(type: newState, savedIndentDepth);
38 qCDebug(formatterLog) << "enter state" << FormatTextStatus::stateToString(type: newState);
39
40 if (newState == StateType::BracketOpen)
41 enterState(newState: StateType::BracketElementStart);
42}
43
44void FormatPartialStatus::leaveState(bool statementDone)
45{
46 Q_ASSERT(currentStatus.size() > 1);
47 if (currentStatus.state().type == StateType::TopmostIntro)
48 return;
49
50 // restore indent depth
51 State poppedState = currentStatus.popState();
52 currentIndent = poppedState.savedIndentDepth;
53
54 StateType topState = currentStatus.state().type;
55
56 qCDebug(formatterLog) << "left state" << FormatTextStatus::stateToString(type: poppedState.type)
57 << ", now in state" << FormatTextStatus::stateToString(type: topState);
58
59 // if statement is done, may need to leave recursively
60 if (statementDone) {
61 if (topState == StateType::IfStatement) {
62 if (poppedState.type != StateType::MaybeElse)
63 enterState(newState: StateType::MaybeElse);
64 else
65 leaveState(statementDone: true);
66 } else if (topState == StateType::ElseClause) {
67 // leave the else *and* the surrounding if, to prevent another else
68 leaveState(statementDone: false);
69 leaveState(statementDone: true);
70 } else if (topState == StateType::TryStatement) {
71 if (poppedState.type != StateType::MaybeCatchOrFinally
72 && poppedState.type != StateType::FinallyStatement) {
73 enterState(newState: StateType::MaybeCatchOrFinally);
74 } else {
75 leaveState(statementDone: true);
76 }
77 } else if (!FormatTextStatus::isExpressionEndState(type: topState)) {
78 leaveState(statementDone: true);
79 }
80 }
81}
82
83void FormatPartialStatus::turnIntoState(StateType newState)
84{
85 leaveState(statementDone: false);
86 enterState(newState);
87}
88
89const Token &FormatPartialStatus::tokenAt(int idx) const
90{
91 static const Token empty;
92 if (idx < 0 || idx >= lineTokens.size())
93 return empty;
94 else
95 return lineTokens.at(i: idx);
96}
97
98int FormatPartialStatus::column(int index) const
99{
100 if (index > line.size())
101 index = line.size();
102 IndentInfo indent(QStringView(line).mid(pos: 0, n: index), options.tabSize, indentOffset);
103 return indent.column;
104}
105
106QStringView FormatPartialStatus::tokenText(const Token &token) const
107{
108 return line.mid(pos: token.begin(), n: token.length);
109}
110
111bool FormatPartialStatus::tryInsideExpression(bool alsoExpression)
112{
113 StateType newState = StateType::Invalid;
114 const int kind = tokenAt(idx: tokenIndex).lexKind;
115 switch (kind) {
116 case QQmlJSGrammar::T_LPAREN:
117 newState = StateType::ParenOpen;
118 break;
119 case QQmlJSGrammar::T_LBRACKET:
120 newState = StateType::BracketOpen;
121 break;
122 case QQmlJSGrammar::T_LBRACE:
123 newState = StateType::ObjectliteralOpen;
124 break;
125 case QQmlJSGrammar::T_FUNCTION:
126 newState = StateType::FunctionStart;
127 break;
128 case QQmlJSGrammar::T_QUESTION:
129 newState = StateType::TernaryOp;
130 break;
131 }
132
133 if (newState != StateType::Invalid) {
134 if (alsoExpression)
135 enterState(newState: StateType::Expression);
136 enterState(newState);
137 return true;
138 }
139
140 return false;
141}
142
143bool FormatPartialStatus::tryStatement()
144{
145 Token t = tokenAt(idx: tokenIndex);
146 const int kind = t.lexKind;
147 switch (kind) {
148 case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
149 case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
150 case QQmlJSGrammar::T_SEMICOLON:
151 enterState(newState: StateType::EmptyStatement);
152 leaveState(statementDone: true);
153 return true;
154 case QQmlJSGrammar::T_BREAK:
155 case QQmlJSGrammar::T_CONTINUE:
156 enterState(newState: StateType::BreakcontinueStatement);
157 return true;
158 case QQmlJSGrammar::T_THROW:
159 enterState(newState: StateType::ThrowStatement);
160 enterState(newState: StateType::Expression);
161 return true;
162 case QQmlJSGrammar::T_RETURN:
163 enterState(newState: StateType::ReturnStatement);
164 enterState(newState: StateType::Expression);
165 return true;
166 case QQmlJSGrammar::T_WHILE:
167 case QQmlJSGrammar::T_FOR:
168 case QQmlJSGrammar::T_CATCH:
169 enterState(newState: StateType::StatementWithCondition);
170 return true;
171 case QQmlJSGrammar::T_SWITCH:
172 enterState(newState: StateType::SwitchStatement);
173 return true;
174 case QQmlJSGrammar::T_IF:
175 enterState(newState: StateType::IfStatement);
176 return true;
177 case QQmlJSGrammar::T_DO:
178 enterState(newState: StateType::DoStatement);
179 enterState(newState: StateType::Substatement);
180 return true;
181 case QQmlJSGrammar::T_CASE:
182 case QQmlJSGrammar::T_DEFAULT:
183 enterState(newState: StateType::CaseStart);
184 return true;
185 case QQmlJSGrammar::T_TRY:
186 enterState(newState: StateType::TryStatement);
187 return true;
188 case QQmlJSGrammar::T_LBRACE:
189 enterState(newState: StateType::JsblockOpen);
190 return true;
191 case QQmlJSGrammar::T_VAR:
192 case QQmlJSGrammar::T_PLUS_PLUS:
193 case QQmlJSGrammar::T_MINUS_MINUS:
194 case QQmlJSGrammar::T_IMPORT:
195 case QQmlJSGrammar::T_SIGNAL:
196 case QQmlJSGrammar::T_ON:
197 case QQmlJSGrammar::T_AS:
198 case QQmlJSGrammar::T_PROPERTY:
199 case QQmlJSGrammar::T_REQUIRED:
200 case QQmlJSGrammar::T_READONLY:
201 case QQmlJSGrammar::T_FUNCTION:
202 case QQmlJSGrammar::T_FUNCTION_STAR:
203 case QQmlJSGrammar::T_NUMERIC_LITERAL:
204 case QQmlJSGrammar::T_LPAREN:
205 enterState(newState: StateType::Expression);
206 // look at the token again
207 tokenIndex -= 1;
208 return true;
209 default:
210 if (Token::lexKindIsIdentifier(kind)) {
211 enterState(newState: StateType::ExpressionOrLabel);
212 return true;
213 } else if (Token::lexKindIsDelimiter(kind) || Token::lexKindIsStringType(kind)) {
214 enterState(newState: StateType::Expression);
215 // look at the token again
216 tokenIndex -= 1;
217 return true;
218 }
219 }
220 return false;
221}
222
223void FormatPartialStatus::dump() const
224{
225 qCDebug(formatterLog) << "Current token index" << tokenIndex;
226 qCDebug(formatterLog) << "Current state:";
227 foreach (const State &s, currentStatus.states) {
228 qCDebug(formatterLog) << FormatTextStatus::stateToString(type: s.type) << s.savedIndentDepth;
229 }
230 qCDebug(formatterLog) << "Current lexerState:" << currentStatus.lexerState.state;
231 qCDebug(formatterLog) << "Current indent:" << currentIndent;
232}
233
234void FormatPartialStatus::handleTokens()
235{
236 auto enter = [this](StateType newState) { this->enterState(newState); };
237
238 auto leave = [this](bool statementDone = false) { this->leaveState(statementDone); };
239
240 auto turnInto = [this](StateType newState) { this->turnIntoState(newState); };
241
242 qCDebug(formatterLog) << "Starting to look at " << line;
243
244 for (; tokenIndex < lineTokens.size();) {
245 Token currentToken = tokenAt(idx: tokenIndex);
246 const int kind = currentToken.lexKind;
247
248 qCDebug(formatterLog) << "Token: " << tokenText(token: currentToken);
249
250 if (Token::lexKindIsComment(kind)
251 && currentStatus.state().type != StateType::MultilineCommentCont
252 && currentStatus.state().type != StateType::MultilineCommentStart) {
253 tokenIndex += 1;
254 continue;
255 }
256
257 switch (currentStatus.state().type) {
258 case StateType::TopmostIntro:
259 switch (kind) {
260 case QQmlJSGrammar::T_IDENTIFIER:
261 enter(StateType::ObjectdefinitionOrJs);
262 continue;
263 case QQmlJSGrammar::T_IMPORT:
264 enter(StateType::TopQml);
265 continue;
266 case QQmlJSGrammar::T_LBRACE:
267 enter(StateType::TopJs);
268 enter(StateType::Expression);
269 continue; // if a file starts with {, it's likely json
270 default:
271 enter(StateType::TopJs);
272 continue;
273 }
274 break;
275
276 case StateType::TopQml:
277 switch (kind) {
278 case QQmlJSGrammar::T_IMPORT:
279 enter(StateType::ImportStart);
280 break;
281 case QQmlJSGrammar::T_IDENTIFIER:
282 enter(StateType::BindingOrObjectdefinition);
283 break;
284 default:
285 if (Token::lexKindIsIdentifier(kind))
286 enter(StateType::BindingOrObjectdefinition);
287 break;
288 }
289 break;
290
291 case StateType::TopJs:
292 tryStatement();
293 break;
294
295 case StateType::ObjectdefinitionOrJs:
296 switch (kind) {
297 case QQmlJSGrammar::T_DOT:
298 break;
299 case QQmlJSGrammar::T_LBRACE:
300 turnInto(StateType::BindingOrObjectdefinition);
301 continue;
302 default:
303 if (!Token::lexKindIsIdentifier(kind) || !line.at(n: currentToken.begin()).isUpper()) {
304 turnInto(StateType::TopJs);
305 continue;
306 }
307 }
308 break;
309
310 case StateType::ImportStart:
311 enter(StateType::ImportMaybeDotOrVersionOrAs);
312 break;
313
314 case StateType::ImportMaybeDotOrVersionOrAs:
315 switch (kind) {
316 case QQmlJSGrammar::T_DOT:
317 turnInto(StateType::ImportDot);
318 break;
319 case QQmlJSGrammar::T_AS:
320 turnInto(StateType::ImportAs);
321 break;
322 case QQmlJSGrammar::T_NUMERIC_LITERAL:
323 case QQmlJSGrammar::T_VERSION_NUMBER:
324 turnInto(StateType::ImportMaybeAs);
325 break;
326 default:
327 leave();
328 leave();
329 continue;
330 }
331 break;
332
333 case StateType::ImportMaybeAs:
334 switch (kind) {
335 case QQmlJSGrammar::T_AS:
336 turnInto(StateType::ImportAs);
337 break;
338 default:
339 leave();
340 leave();
341 continue;
342 }
343 break;
344
345 case StateType::ImportDot:
346 if (Token::lexKindIsIdentifier(kind)) {
347 turnInto(StateType::ImportMaybeDotOrVersionOrAs);
348 } else {
349 leave();
350 leave();
351 continue;
352 }
353 break;
354
355 case StateType::ImportAs:
356 if (Token::lexKindIsIdentifier(kind)) {
357 leave();
358 leave();
359 }
360 break;
361
362 case StateType::BindingOrObjectdefinition:
363 switch (kind) {
364 case QQmlJSGrammar::T_COLON:
365 enter(StateType::BindingAssignment);
366 break;
367 case QQmlJSGrammar::T_LBRACE:
368 enter(StateType::ObjectdefinitionOpen);
369 break;
370 }
371 break;
372
373 case StateType::BindingAssignment:
374 switch (kind) {
375 case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
376 case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
377 case QQmlJSGrammar::T_SEMICOLON:
378 leave(true);
379 break;
380 case QQmlJSGrammar::T_IF:
381 enter(StateType::IfStatement);
382 break;
383 case QQmlJSGrammar::T_WITH:
384 enter(StateType::StatementWithCondition);
385 break;
386 case QQmlJSGrammar::T_TRY:
387 enter(StateType::TryStatement);
388 break;
389 case QQmlJSGrammar::T_SWITCH:
390 enter(StateType::SwitchStatement);
391 break;
392 case QQmlJSGrammar::T_LBRACE:
393 enter(StateType::JsblockOpen);
394 break;
395 case QQmlJSGrammar::T_ON:
396 case QQmlJSGrammar::T_AS:
397 case QQmlJSGrammar::T_IMPORT:
398 case QQmlJSGrammar::T_SIGNAL:
399 case QQmlJSGrammar::T_PROPERTY:
400 case QQmlJSGrammar::T_REQUIRED:
401 case QQmlJSGrammar::T_READONLY:
402 case QQmlJSGrammar::T_IDENTIFIER:
403 enter(StateType::ExpressionOrObjectdefinition);
404 break;
405
406 // error recovery
407 case QQmlJSGrammar::T_RBRACKET:
408 case QQmlJSGrammar::T_RPAREN:
409 leave(true);
410 break;
411
412 default:
413 enter(StateType::Expression);
414 continue;
415 }
416 break;
417
418 case StateType::ObjectdefinitionOpen:
419 switch (kind) {
420 case QQmlJSGrammar::T_RBRACE:
421 leave(true);
422 break;
423 case QQmlJSGrammar::T_DEFAULT:
424 case QQmlJSGrammar::T_READONLY:
425 enter(StateType::PropertyModifiers);
426 break;
427 case QQmlJSGrammar::T_PROPERTY:
428 enter(StateType::PropertyStart);
429 break;
430 case QQmlJSGrammar::T_REQUIRED:
431 enter(StateType::RequiredProperty);
432 break;
433 case QQmlJSGrammar::T_COMPONENT:
434 enter(StateType::ComponentStart);
435 break;
436 case QQmlJSGrammar::T_FUNCTION:
437 case QQmlJSGrammar::T_FUNCTION_STAR:
438 enter(StateType::FunctionStart);
439 break;
440 case QQmlJSGrammar::T_SIGNAL:
441 enter(StateType::SignalStart);
442 break;
443 case QQmlJSGrammar::T_ENUM:
444 enter(StateType::EnumStart);
445 break;
446 case QQmlJSGrammar::T_ON:
447 case QQmlJSGrammar::T_AS:
448 case QQmlJSGrammar::T_IMPORT:
449 enter(StateType::BindingOrObjectdefinition);
450 break;
451 default:
452 if (Token::lexKindIsIdentifier(kind))
453 enter(StateType::BindingOrObjectdefinition);
454 break;
455 }
456 break;
457
458 case StateType::PropertyModifiers:
459 switch (kind) {
460 case QQmlJSGrammar::T_PROPERTY:
461 turnInto(StateType::PropertyStart);
462 break;
463 case QQmlJSGrammar::T_DEFAULT:
464 case QQmlJSGrammar::T_READONLY:
465 break;
466 case QQmlJSGrammar::T_REQUIRED:
467 turnInto(StateType::RequiredProperty);
468 break;
469 default:
470 leave(true);
471 break;
472 }
473 break;
474
475 case StateType::PropertyStart:
476 switch (kind) {
477 case QQmlJSGrammar::T_COLON:
478 enter(StateType::BindingAssignment);
479 break; // oops, was a binding
480 case QQmlJSGrammar::T_VAR:
481 case QQmlJSGrammar::T_IDENTIFIER:
482 enter(StateType::PropertyName);
483 break;
484 default:
485 if (Token::lexKindIsIdentifier(kind) && tokenText(token: currentToken) == u"list") {
486 enter(StateType::PropertyListOpen);
487 } else {
488 leave(true);
489 continue;
490 }
491 }
492 break;
493
494 case StateType::RequiredProperty:
495 switch (kind) {
496 case QQmlJSGrammar::T_PROPERTY:
497 turnInto(StateType::PropertyStart);
498 break;
499 case QQmlJSGrammar::T_DEFAULT:
500 case QQmlJSGrammar::T_READONLY:
501 turnInto(StateType::PropertyModifiers);
502 break;
503 case QQmlJSGrammar::T_IDENTIFIER:
504 leave(true);
505 break;
506 default:
507 leave(true);
508 continue;
509 }
510 break;
511
512 case StateType::ComponentStart:
513 switch (kind) {
514 case QQmlJSGrammar::T_IDENTIFIER:
515 turnInto(StateType::ComponentName);
516 break;
517 default:
518 leave(true);
519 continue;
520 }
521 break;
522
523 case StateType::ComponentName:
524 switch (kind) {
525 case QQmlJSGrammar::T_COLON:
526 enter(StateType::BindingAssignment);
527 break;
528 default:
529 leave(true);
530 continue;
531 }
532 break;
533
534 case StateType::PropertyName:
535 turnInto(StateType::PropertyMaybeInitializer);
536 break;
537
538 case StateType::PropertyListOpen: {
539 const QStringView tok = tokenText(token: currentToken);
540 if (tok == u">")
541 turnInto(StateType::PropertyName);
542 break;
543 }
544 case StateType::PropertyMaybeInitializer:
545 switch (kind) {
546 case QQmlJSGrammar::T_COLON:
547 turnInto(StateType::BindingAssignment);
548 break;
549 default:
550 leave(true);
551 continue;
552 }
553 break;
554
555 case StateType::EnumStart:
556 switch (kind) {
557 case QQmlJSGrammar::T_LBRACE:
558 enter(StateType::ObjectliteralOpen);
559 break;
560 }
561 break;
562
563 case StateType::SignalStart:
564 switch (kind) {
565 case QQmlJSGrammar::T_COLON:
566 enter(StateType::BindingAssignment);
567 break; // oops, was a binding
568 default:
569 enter(StateType::SignalMaybeArglist);
570 break;
571 }
572 break;
573
574 case StateType::SignalMaybeArglist:
575 switch (kind) {
576 case QQmlJSGrammar::T_LPAREN:
577 turnInto(StateType::SignalArglistOpen);
578 break;
579 default:
580 leave(true);
581 continue;
582 }
583 break;
584
585 case StateType::SignalArglistOpen:
586 switch (kind) {
587 case QQmlJSGrammar::T_RPAREN:
588 leave(true);
589 break;
590 }
591 break;
592
593 case StateType::FunctionStart:
594 switch (kind) {
595 case QQmlJSGrammar::T_LPAREN:
596 enter(StateType::FunctionArglistOpen);
597 break;
598 }
599 break;
600
601 case StateType::FunctionArglistOpen:
602 switch (kind) {
603 case QQmlJSGrammar::T_COLON:
604 enter(StateType::TypeAnnotation);
605 break;
606 case QQmlJSGrammar::T_RPAREN:
607 turnInto(StateType::FunctionArglistClosed);
608 break;
609 }
610 break;
611
612 case StateType::FunctionArglistClosed:
613 switch (kind) {
614 case QQmlJSGrammar::T_COLON:
615 enter(StateType::TypeAnnotation);
616 break;
617 case QQmlJSGrammar::T_LBRACE:
618 turnInto(StateType::JsblockOpen);
619 break;
620 default:
621 leave(true);
622 continue; // error recovery
623 }
624 break;
625
626 case StateType::TypeAnnotation:
627 switch (kind) {
628 case QQmlJSGrammar::T_IDENTIFIER:
629 case QQmlJSGrammar::T_DOT:
630 break;
631 case QQmlJSGrammar::T_LT:
632 turnInto(StateType::TypeParameter);
633 break;
634 default:
635 leave();
636 continue; // error recovery
637 }
638 break;
639
640 case StateType::TypeParameter:
641 switch (kind) {
642 case QQmlJSGrammar::T_LT:
643 enter(StateType::TypeParameter);
644 break;
645 case QQmlJSGrammar::T_GT:
646 leave();
647 break;
648 }
649 break;
650
651 case StateType::ExpressionOrObjectdefinition:
652 switch (kind) {
653 case QQmlJSGrammar::T_DOT:
654 break; // need to become an objectdefinition_open in cases like "width: Qt.Foo
655 // {"
656 case QQmlJSGrammar::T_LBRACE:
657 turnInto(StateType::ObjectdefinitionOpen);
658 break;
659
660 // propagate 'leave' from expression state
661 case QQmlJSGrammar::T_RBRACKET:
662 case QQmlJSGrammar::T_RPAREN:
663 leave();
664 continue;
665
666 default:
667 if (Token::lexKindIsIdentifier(kind))
668 break; // need to become an objectdefinition_open in cases like "width:
669 // Qt.Foo
670 enter(StateType::Expression);
671 continue; // really? identifier and more tokens might already be gone
672 }
673 break;
674
675 case StateType::ExpressionOrLabel:
676 switch (kind) {
677 case QQmlJSGrammar::T_COLON:
678 turnInto(StateType::LabelledStatement);
679 break;
680
681 // propagate 'leave' from expression state
682 case QQmlJSGrammar::T_RBRACKET:
683 case QQmlJSGrammar::T_RPAREN:
684 leave();
685 continue;
686
687 default:
688 enter(StateType::Expression);
689 continue;
690 }
691 break;
692
693 case StateType::TernaryOp:
694 if (kind == QQmlJSGrammar::T_COLON) {
695 enter(StateType::TernaryOpAfterColon);
696 enter(StateType::ExpressionContinuation);
697 break;
698 }
699 Q_FALLTHROUGH();
700 case StateType::TernaryOpAfterColon:
701 case StateType::Expression:
702 if (tryInsideExpression(alsoExpression: false))
703 break;
704 switch (kind) {
705 case QQmlJSGrammar::T_COMMA:
706 leave(true);
707 break;
708 case QQmlJSGrammar::T_RBRACKET:
709 case QQmlJSGrammar::T_RPAREN:
710 leave();
711 continue;
712 case QQmlJSGrammar::T_RBRACE:
713 leave(true);
714 continue;
715 case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
716 case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
717 case QQmlJSGrammar::T_SEMICOLON:
718 leave(true);
719 break;
720 default:
721 if (Token::lexKindIsDelimiter(kind))
722 enter(StateType::ExpressionContinuation);
723 break;
724 }
725 break;
726
727 case StateType::ExpressionContinuation:
728 leave();
729 continue;
730
731 case StateType::ExpressionMaybeContinuation:
732 switch (kind) {
733 case QQmlJSGrammar::T_QUESTION:
734 case QQmlJSGrammar::T_LBRACKET:
735 case QQmlJSGrammar::T_LPAREN:
736 case QQmlJSGrammar::T_LBRACE:
737 leave();
738 continue;
739 default:
740 leave(!Token::lexKindIsDelimiter(kind));
741 continue;
742 }
743 break;
744
745 case StateType::ParenOpen:
746 if (tryInsideExpression(alsoExpression: false))
747 break;
748 switch (kind) {
749 case QQmlJSGrammar::T_RPAREN:
750 leave();
751 break;
752 }
753 break;
754
755 case StateType::BracketOpen:
756 if (tryInsideExpression(alsoExpression: false))
757 break;
758 switch (kind) {
759 case QQmlJSGrammar::T_COMMA:
760 enter(StateType::BracketElementStart);
761 break;
762 case QQmlJSGrammar::T_RBRACKET:
763 leave();
764 break;
765 }
766 break;
767
768 case StateType::ObjectliteralOpen:
769 if (tryInsideExpression(alsoExpression: false))
770 break;
771 switch (kind) {
772 case QQmlJSGrammar::T_COLON:
773 enter(StateType::ObjectliteralAssignment);
774 break;
775 case QQmlJSGrammar::T_RBRACKET:
776 case QQmlJSGrammar::T_RPAREN:
777 leave();
778 continue; // error recovery
779 case QQmlJSGrammar::T_RBRACE:
780 leave(true);
781 break;
782 }
783 break;
784
785 // pretty much like expression, but ends with , or }
786 case StateType::ObjectliteralAssignment:
787 if (tryInsideExpression(alsoExpression: false))
788 break;
789 switch (kind) {
790 case QQmlJSGrammar::T_COMMA:
791 leave();
792 break;
793 case QQmlJSGrammar::T_RBRACKET:
794 case QQmlJSGrammar::T_RPAREN:
795 leave();
796 continue; // error recovery
797 case QQmlJSGrammar::T_RBRACE:
798 leave();
799 continue; // so we also leave objectliteral_open
800 default:
801 if (Token::lexKindIsDelimiter(kind))
802 enter(StateType::ExpressionContinuation);
803 break;
804 }
805 break;
806
807 case StateType::BracketElementStart:
808 if (Token::lexKindIsIdentifier(kind)) {
809 turnInto(StateType::BracketElementMaybeObjectdefinition);
810 } else {
811 leave();
812 continue;
813 }
814 break;
815
816 case StateType::BracketElementMaybeObjectdefinition:
817 switch (kind) {
818 case QQmlJSGrammar::T_LBRACE:
819 turnInto(StateType::ObjectdefinitionOpen);
820 break;
821 default:
822 leave();
823 continue;
824 }
825 break;
826
827 case StateType::JsblockOpen:
828 case StateType::SubstatementOpen:
829 if (tryStatement())
830 break;
831 switch (kind) {
832 case QQmlJSGrammar::T_RBRACE:
833 leave(true);
834 break;
835 }
836 break;
837
838 case StateType::LabelledStatement:
839 if (tryStatement())
840 break;
841 leave(true); // error recovery
842 break;
843
844 case StateType::Substatement:
845 // prefer substatement_open over block_open
846 if (kind != QQmlJSGrammar::T_LBRACE) {
847 if (tryStatement())
848 break;
849 }
850 switch (kind) {
851 case QQmlJSGrammar::T_LBRACE:
852 turnInto(StateType::SubstatementOpen);
853 break;
854 }
855 break;
856
857 case StateType::IfStatement:
858 switch (kind) {
859 case QQmlJSGrammar::T_LPAREN:
860 enter(StateType::ConditionOpen);
861 break;
862 default:
863 leave(true);
864 break; // error recovery
865 }
866 break;
867
868 case StateType::MaybeElse:
869 switch (kind) {
870 case QQmlJSGrammar::T_ELSE:
871 turnInto(StateType::ElseClause);
872 enter(StateType::Substatement);
873 break;
874 default:
875 leave(true);
876 continue;
877 }
878 break;
879
880 case StateType::MaybeCatchOrFinally:
881 switch (kind) {
882 case QQmlJSGrammar::T_CATCH:
883 turnInto(StateType::CatchStatement);
884 break;
885 case QQmlJSGrammar::T_FINALLY:
886 turnInto(StateType::FinallyStatement);
887 break;
888 default:
889 leave(true);
890 continue;
891 }
892 break;
893
894 case StateType::ElseClause:
895 // ### shouldn't happen
896 dump();
897 Q_ASSERT(false);
898 leave(true);
899 break;
900
901 case StateType::ConditionOpen:
902 if (tryInsideExpression(alsoExpression: false))
903 break;
904 switch (kind) {
905 case QQmlJSGrammar::T_RPAREN:
906 turnInto(StateType::Substatement);
907 break;
908 }
909 break;
910
911 case StateType::SwitchStatement:
912 case StateType::CatchStatement:
913 case StateType::StatementWithCondition:
914 switch (kind) {
915 case QQmlJSGrammar::T_LPAREN:
916 enter(StateType::StatementWithConditionParenOpen);
917 break;
918 default:
919 leave(true);
920 }
921 break;
922
923 case StateType::StatementWithConditionParenOpen:
924 if (tryInsideExpression(alsoExpression: false))
925 break;
926 switch (kind) {
927 case QQmlJSGrammar::T_RPAREN:
928 turnInto(StateType::Substatement);
929 break;
930 }
931 break;
932
933 case StateType::TryStatement:
934 case StateType::FinallyStatement:
935 switch (kind) {
936 case QQmlJSGrammar::T_LBRACE:
937 enter(StateType::JsblockOpen);
938 break;
939 default:
940 leave(true);
941 break;
942 }
943 break;
944
945 case StateType::DoStatement:
946 switch (kind) {
947 case QQmlJSGrammar::T_WHILE:
948 break;
949 case QQmlJSGrammar::T_LPAREN:
950 enter(StateType::DoStatementWhileParenOpen);
951 break;
952 default:
953 leave(true);
954 continue; // error recovery
955 }
956 break;
957
958 case StateType::DoStatementWhileParenOpen:
959 if (tryInsideExpression(alsoExpression: false))
960 break;
961 switch (kind) {
962 case QQmlJSGrammar::T_RPAREN:
963 leave();
964 leave(true);
965 break;
966 }
967 break;
968
969 case StateType::BreakcontinueStatement:
970 if (Token ::lexKindIsIdentifier(kind)) {
971 leave(true);
972 } else {
973 leave(true);
974 continue; // try again
975 }
976 break;
977
978 case StateType::CaseStart:
979 switch (kind) {
980 case QQmlJSGrammar::T_COLON:
981 turnInto(StateType::CaseCont);
982 break;
983 }
984 break;
985
986 case StateType::CaseCont:
987 if (kind != QQmlJSGrammar::T_CASE && kind != QQmlJSGrammar::T_DEFAULT && tryStatement())
988 break;
989 switch (kind) {
990 case QQmlJSGrammar::T_RBRACE:
991 leave();
992 continue;
993 case QQmlJSGrammar::T_DEFAULT:
994 case QQmlJSGrammar::T_CASE:
995 leave();
996 continue;
997 }
998 break;
999
1000 case StateType::MultilineCommentStart:
1001 case StateType::MultilineCommentCont:
1002 if (!Token::lexKindIsComment(kind)) {
1003 leave();
1004 continue;
1005 } else if (tokenIndex == lineTokens.size() - 1
1006 && !currentStatus.lexerState.isMultiline()) {
1007 leave();
1008 } else if (tokenIndex == 0) {
1009 // to allow enter/leave to update the indentDepth
1010 turnInto(StateType::MultilineCommentCont);
1011 }
1012 break;
1013
1014 default:
1015 qWarning() << "Unhandled state" << currentStatus.state().typeStr();
1016 break;
1017 } // end of state switch
1018
1019 ++tokenIndex;
1020 }
1021
1022 StateType topState = currentStatus.state().type;
1023
1024 // if there's no colon on the same line, it's not a label
1025 if (topState == StateType::ExpressionOrLabel)
1026 enterState(newState: StateType::Expression);
1027 // if not followed by an identifier on the same line, it's done
1028 else if (topState == StateType::BreakcontinueStatement)
1029 leaveState(statementDone: true);
1030
1031 topState = currentStatus.state().type;
1032
1033 // some states might be continued on the next line
1034 if (topState == StateType::Expression || topState == StateType::ExpressionOrObjectdefinition
1035 || topState == StateType::ObjectliteralAssignment
1036 || topState == StateType::TernaryOpAfterColon) {
1037 enterState(newState: StateType::ExpressionMaybeContinuation);
1038 }
1039 // multi-line comment start?
1040 if (topState != StateType::MultilineCommentStart && topState != StateType::MultilineCommentCont
1041 && currentStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_COMMENT) {
1042 enterState(newState: StateType::MultilineCommentStart);
1043 }
1044 currentStatus.finalIndent = currentIndent;
1045}
1046
1047// adjusts the indentation of the current line based on the status of the previous one, and what
1048// it starts with
1049int indentForLineStartingWithToken(const FormatTextStatus &oldStatus, const FormatOptions &,
1050 int tokenKind)
1051{
1052 State topState = oldStatus.state();
1053 State previousState = oldStatus.state(belowTop: 1);
1054 int indentDepth = oldStatus.finalIndent;
1055
1056 // keep user-adjusted indent in multiline comments
1057 if (topState.type == StateType::MultilineCommentStart
1058 || topState.type == StateType::MultilineCommentCont) {
1059 if (!Token::lexKindIsInvalid(kind: tokenKind))
1060 return -1;
1061 }
1062 // don't touch multi-line strings at all
1063 if (oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL
1064 || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL
1065 || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD
1066 || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE) {
1067 return -1;
1068 }
1069
1070 switch (tokenKind) {
1071 case QQmlJSGrammar::T_LBRACE:
1072 if (topState.type == StateType::Substatement
1073 || topState.type == StateType::BindingAssignment
1074 || topState.type == StateType::CaseCont) {
1075 return topState.savedIndentDepth;
1076 }
1077 break;
1078 case QQmlJSGrammar::T_RBRACE: {
1079 if (topState.type == StateType::JsblockOpen && previousState.type == StateType::CaseCont) {
1080 return previousState.savedIndentDepth;
1081 }
1082 for (int i = 0; oldStatus.state(belowTop: i).type != StateType::TopmostIntro; ++i) {
1083 const StateType type = oldStatus.state(belowTop: i).type;
1084 if (type == StateType::ObjectdefinitionOpen || type == StateType::JsblockOpen
1085 || type == StateType::SubstatementOpen || type == StateType::ObjectliteralOpen) {
1086 return oldStatus.state(belowTop: i).savedIndentDepth;
1087 }
1088 }
1089 break;
1090 }
1091 case QQmlJSGrammar::T_RBRACKET:
1092 for (int i = 0; oldStatus.state(belowTop: i).type != StateType::TopmostIntro; ++i) {
1093 const StateType type = oldStatus.state(belowTop: i).type;
1094 if (type == StateType::BracketOpen) {
1095 return oldStatus.state(belowTop: i).savedIndentDepth;
1096 }
1097 }
1098 break;
1099 case QQmlJSGrammar::T_LBRACKET:
1100 case QQmlJSGrammar::T_LPAREN:
1101 if (topState.type == StateType::ExpressionMaybeContinuation)
1102 return topState.savedIndentDepth;
1103 break;
1104 case QQmlJSGrammar::T_ELSE:
1105 if (topState.type == StateType::MaybeElse) {
1106 return oldStatus.state(belowTop: 1).savedIndentDepth;
1107 } else if (topState.type == StateType::ExpressionMaybeContinuation) {
1108 bool hasElse = false;
1109 for (int i = 1; oldStatus.state(belowTop: i).type != StateType::TopmostIntro; ++i) {
1110 const StateType type = oldStatus.state(belowTop: i).type;
1111 if (type == StateType::ElseClause)
1112 hasElse = true;
1113 if (type == StateType::IfStatement) {
1114 if (hasElse) {
1115 hasElse = false;
1116 } else {
1117 return oldStatus.state(belowTop: i).savedIndentDepth;
1118 }
1119 }
1120 }
1121 }
1122 break;
1123 case QQmlJSGrammar::T_CATCH:
1124 case QQmlJSGrammar::T_FINALLY:
1125 if (topState.type == StateType::MaybeCatchOrFinally)
1126 return oldStatus.state(belowTop: 1).savedIndentDepth;
1127 break;
1128 case QQmlJSGrammar::T_COLON:
1129 if (topState.type == StateType::TernaryOp)
1130 return indentDepth - 2;
1131 break;
1132 case QQmlJSGrammar::T_QUESTION:
1133 if (topState.type == StateType::ExpressionMaybeContinuation)
1134 return topState.savedIndentDepth;
1135 break;
1136
1137 case QQmlJSGrammar::T_DEFAULT:
1138 case QQmlJSGrammar::T_CASE:
1139 for (int i = 0; oldStatus.state(belowTop: i).type != StateType::TopmostIntro; ++i) {
1140 const StateType type = oldStatus.state(belowTop: i).type;
1141 if (type == StateType::SwitchStatement || type == StateType::CaseCont) {
1142 return oldStatus.state(belowTop: i).savedIndentDepth;
1143 } else if (type == StateType::TopmostIntro) {
1144 break;
1145 }
1146 }
1147 break;
1148 default:
1149 if (Token::lexKindIsDelimiter(kind: tokenKind)
1150 && topState.type == StateType::ExpressionMaybeContinuation)
1151 return topState.savedIndentDepth;
1152
1153 break;
1154 }
1155 return indentDepth;
1156}
1157
1158// sets currentIndent to the correct indent for the current line
1159int FormatPartialStatus::indentLine()
1160{
1161 Q_ASSERT(currentStatus.size() >= 1);
1162 int firstToken = (lineTokens.isEmpty() ? QQmlJSGrammar::T_NONE : tokenAt(0).lexKind);
1163 int indent = indentForLineStartingWithToken(initialStatus, options, firstToken);
1164 recalculateWithIndent(indent);
1165 return indent;
1166}
1167
1168int FormatPartialStatus::indentForNewLineAfter() const
1169{
1170 // should be just currentIndent?
1171 int indent = indentForLineStartingWithToken(currentStatus, options, QQmlJSGrammar::T_NONE);
1172 if (indent < 0)
1173 return currentIndent;
1174 return indent;
1175}
1176
1177void FormatPartialStatus::recalculateWithIndent(int indent)
1178{
1179 if (indent >= 0) {
1180 indentOffset = 0;
1181 int i = 0;
1182 while (i < line.size() && line.at(n: i).isSpace())
1183 ++i;
1184 indentOffset = indent - column(index: i);
1185 }
1186 currentIndent = initialStatus.finalIndent;
1187 auto lexerState = currentStatus.lexerState;
1188 currentStatus = initialStatus;
1189 currentStatus.lexerState = lexerState;
1190 tokenIndex = 0;
1191 handleTokens();
1192}
1193
1194FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options,
1195 const FormatTextStatus &initialStatus)
1196{
1197 FormatPartialStatus status(line, options, initialStatus);
1198
1199 status.handleTokens();
1200
1201 return status;
1202}
1203
1204void FormatPartialStatus::defaultOnEnter(StateType newState, int *indentDepth,
1205 int *savedIndentDepth) const
1206{
1207 const State &parentState = currentStatus.state();
1208 const Token &tk = tokenAt(idx: tokenIndex);
1209 const int tokenPosition = column(index: tk.begin());
1210 const bool firstToken = (tokenIndex == 0);
1211 const bool lastToken = (tokenIndex == lineTokens.size() - 1);
1212
1213 switch (newState) {
1214 case StateType::ObjectdefinitionOpen: {
1215 // special case for things like "gradient: Gradient {"
1216 if (parentState.type == StateType::BindingAssignment)
1217 *savedIndentDepth = currentStatus.state(belowTop: 1).savedIndentDepth;
1218
1219 if (firstToken)
1220 *savedIndentDepth = tokenPosition;
1221
1222 *indentDepth = *savedIndentDepth + options.indentSize;
1223 break;
1224 }
1225
1226 case StateType::BindingOrObjectdefinition:
1227 if (firstToken)
1228 *indentDepth = *savedIndentDepth = tokenPosition;
1229 break;
1230
1231 case StateType::BindingAssignment:
1232 case StateType::ObjectliteralAssignment:
1233 if (lastToken)
1234 *indentDepth = *savedIndentDepth + options.indentSize;
1235 else
1236 *indentDepth = column(index: tokenAt(idx: tokenIndex + 1).begin());
1237 break;
1238
1239 case StateType::ExpressionOrObjectdefinition:
1240 *indentDepth = tokenPosition;
1241 break;
1242
1243 case StateType::ExpressionOrLabel:
1244 if (*indentDepth == tokenPosition)
1245 *indentDepth += 2 * options.indentSize;
1246 else
1247 *indentDepth = tokenPosition;
1248 break;
1249
1250 case StateType::Expression:
1251 if (*indentDepth == tokenPosition) {
1252 // expression_or_objectdefinition doesn't want the indent
1253 // expression_or_label already has it
1254 if (parentState.type != StateType::ExpressionOrObjectdefinition
1255 && parentState.type != StateType::ExpressionOrLabel
1256 && parentState.type != StateType::BindingAssignment) {
1257 *indentDepth += 2 * options.indentSize;
1258 }
1259 }
1260 // expression_or_objectdefinition and expression_or_label have already consumed the
1261 // first token
1262 else if (parentState.type != StateType::ExpressionOrObjectdefinition
1263 && parentState.type != StateType::ExpressionOrLabel) {
1264 *indentDepth = tokenPosition;
1265 }
1266 break;
1267
1268 case StateType::ExpressionMaybeContinuation:
1269 // set indent depth to indent we'd get if the expression ended here
1270 for (int i = 1; currentStatus.state(belowTop: i).type != StateType::TopmostIntro; ++i) {
1271 const StateType type = currentStatus.state(belowTop: i).type;
1272 if (FormatTextStatus::isExpressionEndState(type)
1273 && !FormatTextStatus::isBracelessState(type)) {
1274 *indentDepth = currentStatus.state(belowTop: i - 1).savedIndentDepth;
1275 break;
1276 }
1277 }
1278 break;
1279
1280 case StateType::BracketOpen:
1281 if (parentState.type == StateType::Expression
1282 && currentStatus.state(belowTop: 1).type == StateType::BindingAssignment) {
1283 *savedIndentDepth = currentStatus.state(belowTop: 2).savedIndentDepth;
1284 *indentDepth = *savedIndentDepth + options.indentSize;
1285 } else if (parentState.type == StateType::ObjectliteralAssignment) {
1286 *savedIndentDepth = parentState.savedIndentDepth;
1287 *indentDepth = *savedIndentDepth + options.indentSize;
1288 } else if (!lastToken) {
1289 *indentDepth = tokenPosition + 1;
1290 } else {
1291 *indentDepth = *savedIndentDepth + options.indentSize;
1292 }
1293 break;
1294
1295 case StateType::FunctionStart:
1296 // align to the beginning of the line
1297 *savedIndentDepth = *indentDepth = column(index: tokenAt(idx: 0).begin());
1298 break;
1299
1300 case StateType::DoStatementWhileParenOpen:
1301 case StateType::StatementWithConditionParenOpen:
1302 case StateType::SignalArglistOpen:
1303 case StateType::FunctionArglistOpen:
1304 case StateType::ParenOpen:
1305 if (!lastToken)
1306 *indentDepth = tokenPosition + 1;
1307 else
1308 *indentDepth += options.indentSize;
1309 break;
1310
1311 case StateType::TernaryOp:
1312 if (!lastToken)
1313 *indentDepth = tokenPosition + tk.length + 1;
1314 else
1315 *indentDepth += options.indentSize;
1316 break;
1317
1318 case StateType::JsblockOpen:
1319 // closing brace should be aligned to case
1320 if (parentState.type == StateType::CaseCont) {
1321 *savedIndentDepth = parentState.savedIndentDepth;
1322 break;
1323 }
1324 Q_FALLTHROUGH();
1325 case StateType::SubstatementOpen:
1326 // special case for "foo: {" and "property int foo: {"
1327 if (parentState.type == StateType::BindingAssignment)
1328 *savedIndentDepth = currentStatus.state(belowTop: 1).savedIndentDepth;
1329 *indentDepth = *savedIndentDepth + options.indentSize;
1330 break;
1331
1332 case StateType::Substatement:
1333 *indentDepth += options.indentSize;
1334 break;
1335
1336 case StateType::ObjectliteralOpen:
1337 if (parentState.type == StateType::Expression
1338 || parentState.type == StateType::ObjectliteralAssignment) {
1339 // undo the continuation indent of the expression
1340 if (currentStatus.state(belowTop: 1).type == StateType::ExpressionOrLabel)
1341 *indentDepth = currentStatus.state(belowTop: 1).savedIndentDepth;
1342 else
1343 *indentDepth = parentState.savedIndentDepth;
1344 *savedIndentDepth = *indentDepth;
1345 }
1346 *indentDepth += options.indentSize;
1347 break;
1348
1349 case StateType::StatementWithCondition:
1350 case StateType::TryStatement:
1351 case StateType::CatchStatement:
1352 case StateType::FinallyStatement:
1353 case StateType::IfStatement:
1354 case StateType::DoStatement:
1355 case StateType::SwitchStatement:
1356 if (firstToken || parentState.type == StateType::BindingAssignment)
1357 *savedIndentDepth = tokenPosition;
1358 // ### continuation
1359 *indentDepth = *savedIndentDepth; // + 2*options.indentSize;
1360 // special case for 'else if'
1361 if (!firstToken && newState == StateType::IfStatement
1362 && parentState.type == StateType::Substatement
1363 && currentStatus.state(belowTop: 1).type == StateType::ElseClause) {
1364 *indentDepth = currentStatus.state(belowTop: 1).savedIndentDepth;
1365 *savedIndentDepth = *indentDepth;
1366 }
1367 break;
1368
1369 case StateType::MaybeElse:
1370 case StateType::MaybeCatchOrFinally: {
1371 // set indent to where leave(true) would put it
1372 int lastNonEndState = 0;
1373 while (!FormatTextStatus::isExpressionEndState(
1374 type: currentStatus.state(belowTop: lastNonEndState + 1).type))
1375 ++lastNonEndState;
1376 *indentDepth = currentStatus.state(belowTop: lastNonEndState).savedIndentDepth;
1377 break;
1378 }
1379
1380 case StateType::ConditionOpen:
1381 // fixed extra indent when continuing 'if (', but not for 'else if ('
1382 if (tokenPosition <= *indentDepth + options.indentSize)
1383 *indentDepth += 2 * options.indentSize;
1384 else
1385 *indentDepth = tokenPosition + 1;
1386 break;
1387
1388 case StateType::CaseStart:
1389 *savedIndentDepth = tokenPosition;
1390 break;
1391
1392 case StateType::CaseCont:
1393 *indentDepth += options.indentSize;
1394 break;
1395
1396 case StateType::MultilineCommentStart:
1397 *indentDepth = tokenPosition + 2;
1398 break;
1399
1400 case StateType::MultilineCommentCont:
1401 *indentDepth = tokenPosition;
1402 break;
1403 default:
1404 break;
1405 }
1406}
1407
1408} // namespace Dom
1409} // namespace QQmlJS
1410QT_END_NAMESPACE
1411

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