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
9Q_STATIC_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_NUMERIC_LITERAL:
203 case QQmlJSGrammar::T_LPAREN:
204 enterState(newState: StateType::Expression);
205 // look at the token again
206 tokenIndex -= 1;
207 return true;
208 default:
209 if (Token::lexKindIsIdentifier(kind)) {
210 enterState(newState: StateType::ExpressionOrLabel);
211 return true;
212 } else if (Token::lexKindIsDelimiter(kind) || Token::lexKindIsStringType(kind)) {
213 enterState(newState: StateType::Expression);
214 // look at the token again
215 tokenIndex -= 1;
216 return true;
217 }
218 }
219 return false;
220}
221
222void FormatPartialStatus::dump() const
223{
224 qCDebug(formatterLog) << "Current token index" << tokenIndex;
225 qCDebug(formatterLog) << "Current state:";
226 for (const State &s : currentStatus.states)
227 qCDebug(formatterLog) << FormatTextStatus::stateToString(type: s.type) << s.savedIndentDepth;
228 qCDebug(formatterLog) << "Current lexerState:" << currentStatus.lexerState.state;
229 qCDebug(formatterLog) << "Current indent:" << currentIndent;
230}
231
232void FormatPartialStatus::handleTokens()
233{
234 auto enter = [this](StateType newState) { this->enterState(newState); };
235
236 auto leave = [this](bool statementDone = false) { this->leaveState(statementDone); };
237
238 auto turnInto = [this](StateType newState) { this->turnIntoState(newState); };
239
240 qCDebug(formatterLog) << "Starting to look at " << line;
241
242 for (; tokenIndex < lineTokens.size();) {
243 Token currentToken = tokenAt(idx: tokenIndex);
244 const int kind = currentToken.lexKind;
245
246 qCDebug(formatterLog) << "Token: " << tokenText(token: currentToken);
247
248 if (Token::lexKindIsComment(kind)
249 && currentStatus.state().type != StateType::MultilineCommentCont
250 && currentStatus.state().type != StateType::MultilineCommentStart) {
251 tokenIndex += 1;
252 continue;
253 }
254
255 switch (currentStatus.state().type) {
256 case StateType::TopmostIntro:
257 switch (kind) {
258 case QQmlJSGrammar::T_IDENTIFIER:
259 enter(StateType::ObjectdefinitionOrJs);
260 continue;
261 case QQmlJSGrammar::T_IMPORT:
262 enter(StateType::TopQml);
263 continue;
264 case QQmlJSGrammar::T_LBRACE:
265 enter(StateType::TopJs);
266 enter(StateType::Expression);
267 continue; // if a file starts with {, it's likely json
268 default:
269 enter(StateType::TopJs);
270 continue;
271 }
272 break;
273
274 case StateType::TopQml:
275 switch (kind) {
276 case QQmlJSGrammar::T_IMPORT:
277 enter(StateType::ImportStart);
278 break;
279 case QQmlJSGrammar::T_IDENTIFIER:
280 enter(StateType::BindingOrObjectdefinition);
281 break;
282 default:
283 if (Token::lexKindIsIdentifier(kind))
284 enter(StateType::BindingOrObjectdefinition);
285 break;
286 }
287 break;
288
289 case StateType::TopJs:
290 tryStatement();
291 break;
292
293 case StateType::ObjectdefinitionOrJs:
294 switch (kind) {
295 case QQmlJSGrammar::T_DOT:
296 break;
297 case QQmlJSGrammar::T_LBRACE:
298 turnInto(StateType::BindingOrObjectdefinition);
299 continue;
300 default:
301 if (!Token::lexKindIsIdentifier(kind) || !line.at(n: currentToken.begin()).isUpper()) {
302 turnInto(StateType::TopJs);
303 continue;
304 }
305 }
306 break;
307
308 case StateType::ImportStart:
309 enter(StateType::ImportMaybeDotOrVersionOrAs);
310 break;
311
312 case StateType::ImportMaybeDotOrVersionOrAs:
313 switch (kind) {
314 case QQmlJSGrammar::T_DOT:
315 turnInto(StateType::ImportDot);
316 break;
317 case QQmlJSGrammar::T_AS:
318 turnInto(StateType::ImportAs);
319 break;
320 case QQmlJSGrammar::T_NUMERIC_LITERAL:
321 case QQmlJSGrammar::T_VERSION_NUMBER:
322 turnInto(StateType::ImportMaybeAs);
323 break;
324 default:
325 leave();
326 leave();
327 continue;
328 }
329 break;
330
331 case StateType::ImportMaybeAs:
332 switch (kind) {
333 case QQmlJSGrammar::T_AS:
334 turnInto(StateType::ImportAs);
335 break;
336 default:
337 leave();
338 leave();
339 continue;
340 }
341 break;
342
343 case StateType::ImportDot:
344 if (Token::lexKindIsIdentifier(kind)) {
345 turnInto(StateType::ImportMaybeDotOrVersionOrAs);
346 } else {
347 leave();
348 leave();
349 continue;
350 }
351 break;
352
353 case StateType::ImportAs:
354 if (Token::lexKindIsIdentifier(kind)) {
355 leave();
356 leave();
357 }
358 break;
359
360 case StateType::BindingOrObjectdefinition:
361 switch (kind) {
362 case QQmlJSGrammar::T_COLON:
363 enter(StateType::BindingAssignment);
364 break;
365 case QQmlJSGrammar::T_LBRACE:
366 enter(StateType::ObjectdefinitionOpen);
367 break;
368 }
369 break;
370
371 case StateType::BindingAssignment:
372 switch (kind) {
373 case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
374 case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
375 case QQmlJSGrammar::T_SEMICOLON:
376 leave(true);
377 break;
378 case QQmlJSGrammar::T_IF:
379 enter(StateType::IfStatement);
380 break;
381 case QQmlJSGrammar::T_WITH:
382 enter(StateType::StatementWithCondition);
383 break;
384 case QQmlJSGrammar::T_TRY:
385 enter(StateType::TryStatement);
386 break;
387 case QQmlJSGrammar::T_SWITCH:
388 enter(StateType::SwitchStatement);
389 break;
390 case QQmlJSGrammar::T_LBRACE:
391 enter(StateType::JsblockOpen);
392 break;
393 case QQmlJSGrammar::T_ON:
394 case QQmlJSGrammar::T_AS:
395 case QQmlJSGrammar::T_IMPORT:
396 case QQmlJSGrammar::T_SIGNAL:
397 case QQmlJSGrammar::T_PROPERTY:
398 case QQmlJSGrammar::T_REQUIRED:
399 case QQmlJSGrammar::T_READONLY:
400 case QQmlJSGrammar::T_IDENTIFIER:
401 enter(StateType::ExpressionOrObjectdefinition);
402 break;
403
404 // error recovery
405 case QQmlJSGrammar::T_RBRACKET:
406 case QQmlJSGrammar::T_RPAREN:
407 leave(true);
408 break;
409
410 default:
411 enter(StateType::Expression);
412 continue;
413 }
414 break;
415
416 case StateType::ObjectdefinitionOpen:
417 switch (kind) {
418 case QQmlJSGrammar::T_RBRACE:
419 leave(true);
420 break;
421 case QQmlJSGrammar::T_DEFAULT:
422 case QQmlJSGrammar::T_READONLY:
423 enter(StateType::PropertyModifiers);
424 break;
425 case QQmlJSGrammar::T_PROPERTY:
426 enter(StateType::PropertyStart);
427 break;
428 case QQmlJSGrammar::T_REQUIRED:
429 enter(StateType::RequiredProperty);
430 break;
431 case QQmlJSGrammar::T_COMPONENT:
432 enter(StateType::ComponentStart);
433 break;
434 case QQmlJSGrammar::T_FUNCTION:
435 enter(StateType::FunctionStart);
436 break;
437 case QQmlJSGrammar::T_SIGNAL:
438 enter(StateType::SignalStart);
439 break;
440 case QQmlJSGrammar::T_ENUM:
441 enter(StateType::EnumStart);
442 break;
443 case QQmlJSGrammar::T_ON:
444 case QQmlJSGrammar::T_AS:
445 case QQmlJSGrammar::T_IMPORT:
446 enter(StateType::BindingOrObjectdefinition);
447 break;
448 default:
449 if (Token::lexKindIsIdentifier(kind))
450 enter(StateType::BindingOrObjectdefinition);
451 break;
452 }
453 break;
454
455 case StateType::PropertyModifiers:
456 switch (kind) {
457 case QQmlJSGrammar::T_PROPERTY:
458 turnInto(StateType::PropertyStart);
459 break;
460 case QQmlJSGrammar::T_DEFAULT:
461 case QQmlJSGrammar::T_READONLY:
462 break;
463 case QQmlJSGrammar::T_REQUIRED:
464 turnInto(StateType::RequiredProperty);
465 break;
466 default:
467 leave(true);
468 break;
469 }
470 break;
471
472 case StateType::PropertyStart:
473 switch (kind) {
474 case QQmlJSGrammar::T_COLON:
475 enter(StateType::BindingAssignment);
476 break; // oops, was a binding
477 case QQmlJSGrammar::T_VAR:
478 case QQmlJSGrammar::T_IDENTIFIER:
479 enter(StateType::PropertyName);
480 break;
481 default:
482 bool kindIsIdentifier = Token::lexKindIsIdentifier(kind);
483 if (kindIsIdentifier && tokenText(token: currentToken) == u"list") {
484 enter(StateType::PropertyListOpen);
485 } else if (kindIsIdentifier) {
486 enter(StateType::PropertyName);
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