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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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