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