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