Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmldomcodeformatter_p.h"
++
++ #include <QLoggingCategory>
++ #include <QMetaEnum>
++
++ Q_STATIC_LOGGING_CATEGORY(formatterLog, "qt.qmldom.formatter", QtWarningMsg);
++
++ QT_BEGIN_NAMESPACE
++ namespace QQmlJS {
++ namespace Dom {
++
++ using StateType = FormatTextStatus::StateType;
++ using State = FormatTextStatus::State;
++
++ State FormatTextStatus::state(int belowTop) const
++ {
++ if (belowTop < states.size())
++ return states.at(states.size() - 1 - belowTop);
++ else
++ return State();
++ }
++
++ QString FormatTextStatus::stateToString(StateType type)
++ {
++ const QMetaEnum &metaEnum =
++ staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType"));
++ return QString::fromUtf8(metaEnum.valueToKey(int(type)));
++ }
++
++ void FormatPartialStatus::enterState(StateType newState)
++ {
++ int savedIndentDepth = currentIndent;
++ defaultOnEnter(newState, ¤tIndent, &savedIndentDepth);
++ currentStatus.pushState(newState, savedIndentDepth);
++ qCDebug(formatterLog) << "enter state" << FormatTextStatus::stateToString(newState);
++
++ if (newState == StateType::BracketOpen)
++ enterState(StateType::BracketElementStart);
++ }
++
++ void FormatPartialStatus::leaveState(bool statementDone)
++ {
++ Q_ASSERT(currentStatus.size() > 1);
++ if (currentStatus.state().type == StateType::TopmostIntro)
++ return;
++
++ // restore indent depth
++ State poppedState = currentStatus.popState();
++ currentIndent = poppedState.savedIndentDepth;
++
++ StateType topState = currentStatus.state().type;
++
++ qCDebug(formatterLog) << "left state" << FormatTextStatus::stateToString(poppedState.type)
++ << ", now in state" << FormatTextStatus::stateToString(topState);
++
++ // if statement is done, may need to leave recursively
++ if (statementDone) {
++ if (topState == StateType::IfStatement) {
++ if (poppedState.type != StateType::MaybeElse)
++ enterState(StateType::MaybeElse);
++ else
++ leaveState(true);
++ } else if (topState == StateType::ElseClause) {
++ // leave the else *and* the surrounding if, to prevent another else
++ leaveState(false);
++ leaveState(true);
++ } else if (topState == StateType::TryStatement) {
++ if (poppedState.type != StateType::MaybeCatchOrFinally
++ && poppedState.type != StateType::FinallyStatement) {
++ enterState(StateType::MaybeCatchOrFinally);
++ } else {
++ leaveState(true);
++ }
++ } else if (!FormatTextStatus::isExpressionEndState(topState)) {
++ leaveState(true);
++ }
++ }
++ }
++
++ void FormatPartialStatus::turnIntoState(StateType newState)
++ {
++ leaveState(false);
++ enterState(newState);
++ }
++
++ const Token &FormatPartialStatus::tokenAt(int idx) const
++ {
++ static const Token empty;
++ if (idx < 0 || idx >= lineTokens.size())
++ return empty;
++ else
++ return lineTokens.at(idx);
++ }
++
++ int FormatPartialStatus::column(int index) const
++ {
++ if (index > line.size())
++ index = line.size();
++ IndentInfo indent(QStringView(line).mid(0, index), options.tabSize, indentOffset);
++ return indent.column;
++ }
++
++ QStringView FormatPartialStatus::tokenText(const Token &token) const
++ {
++ return line.mid(token.begin(), token.length);
++ }
++
++ bool FormatPartialStatus::tryInsideExpression(bool alsoExpression)
++ {
++ StateType newState = StateType::Invalid;
++ const int kind = tokenAt(tokenIndex).lexKind;
++ switch (kind) {
++ case QQmlJSGrammar::T_LPAREN:
++ newState = StateType::ParenOpen;
++ break;
++ case QQmlJSGrammar::T_LBRACKET:
++ newState = StateType::BracketOpen;
++ break;
++ case QQmlJSGrammar::T_LBRACE:
++ newState = StateType::ObjectliteralOpen;
++ break;
++ case QQmlJSGrammar::T_FUNCTION:
++ newState = StateType::FunctionStart;
++ break;
++ case QQmlJSGrammar::T_ARROW:
++ newState = StateType::LambdaStart;
++ break;
++ case QQmlJSGrammar::T_QUESTION:
++ newState = StateType::TernaryOp;
++ break;
++ }
++
++ if (newState != StateType::Invalid) {
++ if (alsoExpression)
++ enterState(StateType::Expression);
++ enterState(newState);
++ return true;
++ }
++
++ return false;
++ }
++
++ bool FormatPartialStatus::tryStatement()
++ {
++ Token t = tokenAt(tokenIndex);
++ const int kind = t.lexKind;
++ switch (kind) {
++ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
++ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
++ case QQmlJSGrammar::T_SEMICOLON:
++ enterState(StateType::EmptyStatement);
++ leaveState(true);
++ return true;
++ case QQmlJSGrammar::T_BREAK:
++ case QQmlJSGrammar::T_CONTINUE:
++ enterState(StateType::BreakcontinueStatement);
++ return true;
++ case QQmlJSGrammar::T_THROW:
++ enterState(StateType::ThrowStatement);
++ enterState(StateType::Expression);
++ return true;
++ case QQmlJSGrammar::T_RETURN:
++ enterState(StateType::ReturnStatement);
++ enterState(StateType::Expression);
++ return true;
++ case QQmlJSGrammar::T_WHILE:
++ case QQmlJSGrammar::T_FOR:
++ case QQmlJSGrammar::T_CATCH:
++ enterState(StateType::StatementWithCondition);
++ return true;
++ case QQmlJSGrammar::T_SWITCH:
++ enterState(StateType::SwitchStatement);
++ return true;
++ case QQmlJSGrammar::T_IF:
++ enterState(StateType::IfStatement);
++ return true;
++ case QQmlJSGrammar::T_DO:
++ enterState(StateType::DoStatement);
++ enterState(StateType::Substatement);
++ return true;
++ case QQmlJSGrammar::T_CASE:
++ case QQmlJSGrammar::T_DEFAULT:
++ enterState(StateType::CaseStart);
++ return true;
++ case QQmlJSGrammar::T_TRY:
++ enterState(StateType::TryStatement);
++ return true;
++ case QQmlJSGrammar::T_LBRACE:
++ enterState(StateType::JsblockOpen);
++ return true;
++ case QQmlJSGrammar::T_VAR:
++ case QQmlJSGrammar::T_PLUS_PLUS:
++ case QQmlJSGrammar::T_MINUS_MINUS:
++ case QQmlJSGrammar::T_IMPORT:
++ case QQmlJSGrammar::T_SIGNAL:
++ case QQmlJSGrammar::T_ON:
++ case QQmlJSGrammar::T_AS:
++ case QQmlJSGrammar::T_PROPERTY:
++ case QQmlJSGrammar::T_REQUIRED:
++ case QQmlJSGrammar::T_READONLY:
++ case QQmlJSGrammar::T_FUNCTION:
++ case QQmlJSGrammar::T_NUMERIC_LITERAL:
++ case QQmlJSGrammar::T_LPAREN:
++ enterState(StateType::Expression);
++ // look at the token again
++ tokenIndex -= 1;
++ return true;
++ default:
++ if (Token::lexKindIsIdentifier(kind)) {
++ enterState(StateType::ExpressionOrLabel);
++ return true;
++ } else if (Token::lexKindIsDelimiter(kind) || Token::lexKindIsStringType(kind)) {
++ enterState(StateType::Expression);
++ // look at the token again
++ tokenIndex -= 1;
++ return true;
++ }
++ }
++ return false;
++ }
++
++ void FormatPartialStatus::dump() const
++ {
++ qCDebug(formatterLog) << "Current token index" << tokenIndex;
++ qCDebug(formatterLog) << "Current state:";
++ for (const State &s : currentStatus.states)
++ qCDebug(formatterLog) << FormatTextStatus::stateToString(s.type) << s.savedIndentDepth;
++ qCDebug(formatterLog) << "Current lexerState:" << currentStatus.lexerState.state;
++ qCDebug(formatterLog) << "Current indent:" << currentIndent;
++ }
++
++ void FormatPartialStatus::handleTokens()
++ {
++ auto enter = [this](StateType newState) { this->enterState(newState); };
++
++ auto leave = [this](bool statementDone = false) { this->leaveState(statementDone); };
++
++ auto turnInto = [this](StateType newState) { this->turnIntoState(newState); };
++
++ qCDebug(formatterLog) << "Starting to look at " << line;
++
++ for (; tokenIndex < lineTokens.size();) {
++ Token currentToken = tokenAt(tokenIndex);
++ const int kind = currentToken.lexKind;
++
++ qCDebug(formatterLog) << "Token: " << tokenText(currentToken);
++
++ if (Token::lexKindIsComment(kind)
++ && currentStatus.state().type != StateType::MultilineCommentCont
++ && currentStatus.state().type != StateType::MultilineCommentStart) {
++ tokenIndex += 1;
++ continue;
++ }
++
++ switch (currentStatus.state().type) {
++ case StateType::TopmostIntro:
++ switch (kind) {
++ case QQmlJSGrammar::T_IDENTIFIER:
++ enter(StateType::ObjectdefinitionOrJs);
++ continue;
++ case QQmlJSGrammar::T_PRAGMA:
++ case QQmlJSGrammar::T_IMPORT:
++ enter(StateType::TopQml);
++ continue;
++ case QQmlJSGrammar::T_LBRACE:
++ enter(StateType::TopJs);
++ enter(StateType::Expression);
++ continue; // if a file starts with {, it's likely json
++ default:
++ enter(StateType::TopJs);
++ continue;
++ }
++ break;
++
++ case StateType::TopQml:
++ switch (kind) {
++ case QQmlJSGrammar::T_PRAGMA:
++ enter(StateType::PragmaStart);
++ break;
++ case QQmlJSGrammar::T_IMPORT:
++ enter(StateType::ImportStart);
++ break;
++ case QQmlJSGrammar::T_IDENTIFIER:
++ enter(StateType::BindingOrObjectdefinition);
++ break;
++ default:
++ if (Token::lexKindIsIdentifier(kind))
++ enter(StateType::BindingOrObjectdefinition);
++ break;
++ }
++ break;
++
++ case StateType::TopJs:
++ tryStatement();
++ break;
++
++ case StateType::ObjectdefinitionOrJs:
++ switch (kind) {
++ case QQmlJSGrammar::T_DOT:
++ break;
++ case QQmlJSGrammar::T_LBRACE:
++ turnInto(StateType::BindingOrObjectdefinition);
++ continue;
++ default:
++ if (!Token::lexKindIsIdentifier(kind) || !line.at(currentToken.begin()).isUpper()) {
++ turnInto(StateType::TopJs);
++ continue;
++ }
++ }
++ break;
++
++ case StateType::PragmaStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_IDENTIFIER:
++ turnInto(StateType::PragmaMaybeValue);
++ break;
++ default:
++ leave();
++ continue;
++ }
++ break;
++ case StateType::PragmaMaybeValue:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ turnInto(StateType::PragmaValue);
++ break;
++ default:
++ leave();
++ continue;
++ }
++ break;
++ case StateType::PragmaValue:
++ switch (kind) {
++ case QQmlJSGrammar::T_IDENTIFIER:
++ case QQmlJSGrammar::T_STRING_LITERAL:
++ turnInto(StateType::PragmaMaybeMoreValues);
++ break;
++ default:
++ leave();
++ continue;
++ }
++ break;
++ case StateType::PragmaMaybeMoreValues:
++ switch (kind) {
++ case QQmlJSGrammar::T_COMMA:
++ turnInto(StateType::PragmaValue);
++ break;
++ default:
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::ImportStart:
++ enter(StateType::ImportMaybeDotOrVersionOrAs);
++ break;
++
++ case StateType::ImportMaybeDotOrVersionOrAs:
++ switch (kind) {
++ case QQmlJSGrammar::T_DOT:
++ turnInto(StateType::ImportDot);
++ break;
++ case QQmlJSGrammar::T_AS:
++ turnInto(StateType::ImportAs);
++ break;
++ case QQmlJSGrammar::T_NUMERIC_LITERAL:
++ case QQmlJSGrammar::T_VERSION_NUMBER:
++ turnInto(StateType::ImportMaybeAs);
++ break;
++ default:
++ leave();
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::ImportMaybeAs:
++ switch (kind) {
++ case QQmlJSGrammar::T_AS:
++ turnInto(StateType::ImportAs);
++ break;
++ default:
++ leave();
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::ImportDot:
++ if (Token::lexKindIsIdentifier(kind)) {
++ turnInto(StateType::ImportMaybeDotOrVersionOrAs);
++ } else {
++ leave();
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::ImportAs:
++ if (Token::lexKindIsIdentifier(kind)) {
++ leave();
++ leave();
++ }
++ break;
++
++ case StateType::BindingOrObjectdefinition:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::BindingAssignment);
++ break;
++ case QQmlJSGrammar::T_LBRACE:
++ enter(StateType::ObjectdefinitionOpen);
++ break;
++ }
++ break;
++
++ case StateType::BindingAssignment:
++ switch (kind) {
++ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
++ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
++ case QQmlJSGrammar::T_SEMICOLON:
++ leave(true);
++ break;
++ case QQmlJSGrammar::T_IF:
++ enter(StateType::IfStatement);
++ break;
++ case QQmlJSGrammar::T_WITH:
++ enter(StateType::StatementWithCondition);
++ break;
++ case QQmlJSGrammar::T_TRY:
++ enter(StateType::TryStatement);
++ break;
++ case QQmlJSGrammar::T_SWITCH:
++ enter(StateType::SwitchStatement);
++ break;
++ case QQmlJSGrammar::T_LBRACE:
++ enter(StateType::JsblockOpen);
++ break;
++ case QQmlJSGrammar::T_ON:
++ case QQmlJSGrammar::T_AS:
++ case QQmlJSGrammar::T_IMPORT:
++ case QQmlJSGrammar::T_SIGNAL:
++ case QQmlJSGrammar::T_PROPERTY:
++ case QQmlJSGrammar::T_REQUIRED:
++ case QQmlJSGrammar::T_READONLY:
++ case QQmlJSGrammar::T_IDENTIFIER:
++ enter(StateType::ExpressionOrObjectdefinition);
++ break;
++
++ // error recovery
++ case QQmlJSGrammar::T_RBRACKET:
++ case QQmlJSGrammar::T_RPAREN:
++ leave(true);
++ break;
++
++ default:
++ enter(StateType::Expression);
++ continue;
++ }
++ break;
++
++ case StateType::ObjectdefinitionOpen:
++ switch (kind) {
++ case QQmlJSGrammar::T_RBRACE:
++ leave(true);
++ break;
++ case QQmlJSGrammar::T_DEFAULT:
++ case QQmlJSGrammar::T_READONLY:
++ enter(StateType::PropertyModifiers);
++ break;
++ case QQmlJSGrammar::T_PROPERTY:
++ enter(StateType::PropertyStart);
++ break;
++ case QQmlJSGrammar::T_REQUIRED:
++ enter(StateType::RequiredProperty);
++ break;
++ case QQmlJSGrammar::T_COMPONENT:
++ enter(StateType::ComponentStart);
++ break;
++ case QQmlJSGrammar::T_FUNCTION:
++ enter(StateType::FunctionStart);
++ break;
++ case QQmlJSGrammar::T_SIGNAL:
++ enter(StateType::SignalStart);
++ break;
++ case QQmlJSGrammar::T_ENUM:
++ enter(StateType::EnumStart);
++ break;
++ case QQmlJSGrammar::T_ON:
++ case QQmlJSGrammar::T_AS:
++ case QQmlJSGrammar::T_IMPORT:
++ enter(StateType::BindingOrObjectdefinition);
++ break;
++ default:
++ if (Token::lexKindIsIdentifier(kind))
++ enter(StateType::BindingOrObjectdefinition);
++ break;
++ }
++ break;
++
++ case StateType::PropertyModifiers:
++ switch (kind) {
++ case QQmlJSGrammar::T_PROPERTY:
++ turnInto(StateType::PropertyStart);
++ break;
++ case QQmlJSGrammar::T_DEFAULT:
++ case QQmlJSGrammar::T_READONLY:
++ break;
++ case QQmlJSGrammar::T_REQUIRED:
++ turnInto(StateType::RequiredProperty);
++ break;
++ default:
++ leave(true);
++ break;
++ }
++ break;
++
++ case StateType::PropertyStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::BindingAssignment);
++ break; // oops, was a binding
++ case QQmlJSGrammar::T_VAR:
++ enter(StateType::PropertyName);
++ break;
++ default:
++ bool kindIsIdentifier = Token::lexKindIsIdentifier(kind);
++ if (kindIsIdentifier && tokenText(currentToken) == u"list") {
++ enter(StateType::PropertyListOpen);
++ } else if (kindIsIdentifier) {
++ enter(StateType::PropertyName);
++ } else {
++ leave(true);
++ continue;
++ }
++ }
++ break;
++
++ case StateType::RequiredProperty:
++ switch (kind) {
++ case QQmlJSGrammar::T_PROPERTY:
++ turnInto(StateType::PropertyStart);
++ break;
++ case QQmlJSGrammar::T_DEFAULT:
++ case QQmlJSGrammar::T_READONLY:
++ turnInto(StateType::PropertyModifiers);
++ break;
++ case QQmlJSGrammar::T_IDENTIFIER:
++ leave(true);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::ComponentStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_IDENTIFIER:
++ turnInto(StateType::ComponentName);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::ComponentName:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::BindingAssignment);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::PropertyName:
++ turnInto(StateType::PropertyMaybeInitializer);
++ break;
++
++ case StateType::PropertyListOpen: {
++ const QStringView tok = tokenText(currentToken);
++ if (tok == u">")
++ turnInto(StateType::PropertyName);
++ break;
++ }
++ case StateType::PropertyMaybeInitializer:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ turnInto(StateType::BindingAssignment);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::EnumStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_LBRACE:
++ enter(StateType::ObjectliteralOpen);
++ break;
++ }
++ break;
++
++ case StateType::SignalStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::BindingAssignment);
++ break; // oops, was a binding
++ default:
++ enter(StateType::SignalMaybeArglist);
++ break;
++ }
++ break;
++
++ case StateType::SignalMaybeArglist:
++ switch (kind) {
++ case QQmlJSGrammar::T_LPAREN:
++ turnInto(StateType::SignalArglistOpen);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::SignalArglistOpen:
++ switch (kind) {
++ case QQmlJSGrammar::T_RPAREN:
++ leave(true);
++ break;
++ }
++ break;
++
++ case StateType::FunctionStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_LPAREN:
++ enter(StateType::FunctionArglistOpen);
++ break;
++ }
++ break;
++
++ case StateType::FunctionArglistOpen:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::TypeAnnotation);
++ break;
++ case QQmlJSGrammar::T_RPAREN:
++ turnInto(StateType::FunctionArglistClosed);
++ break;
++ }
++ break;
++
++ case StateType::FunctionArglistClosed:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::TypeAnnotation);
++ break;
++ case QQmlJSGrammar::T_LBRACE:
++ turnInto(StateType::JsblockOpen);
++ break;
++ default:
++ leave(true);
++ continue; // error recovery
++ }
++ break;
++
++ case StateType::LambdaStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_LBRACE:
++ turnInto(StateType::JsblockOpen);
++ break;
++ default:
++ turnInto(StateType::Expression);
++ }
++ break;
++
++ case StateType::TypeAnnotation:
++ switch (kind) {
++ case QQmlJSGrammar::T_IDENTIFIER:
++ case QQmlJSGrammar::T_VOID:
++ case QQmlJSGrammar::T_VAR:
++ case QQmlJSGrammar::T_DOT:
++ break;
++ case QQmlJSGrammar::T_LT:
++ turnInto(StateType::TypeParameter);
++ break;
++ default:
++ leave();
++ continue; // error recovery
++ }
++ break;
++
++ case StateType::TypeParameter:
++ switch (kind) {
++ case QQmlJSGrammar::T_LT:
++ enter(StateType::TypeParameter);
++ break;
++ case QQmlJSGrammar::T_GT:
++ leave();
++ break;
++ }
++ break;
++
++ case StateType::ExpressionOrObjectdefinition:
++ switch (kind) {
++ case QQmlJSGrammar::T_DOT:
++ break; // need to become an objectdefinition_open in cases like "width: Qt.Foo
++ // {"
++ case QQmlJSGrammar::T_LBRACE:
++ turnInto(StateType::ObjectdefinitionOpen);
++ break;
++
++ // propagate 'leave' from expression state
++ case QQmlJSGrammar::T_RBRACKET:
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ continue;
++
++ default:
++ if (Token::lexKindIsIdentifier(kind))
++ break; // need to become an objectdefinition_open in cases like "width:
++ // Qt.Foo
++ enter(StateType::Expression);
++ continue; // really? identifier and more tokens might already be gone
++ }
++ break;
++
++ case StateType::ExpressionOrLabel:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ turnInto(StateType::LabelledStatement);
++ break;
++
++ // propagate 'leave' from expression state
++ case QQmlJSGrammar::T_RBRACKET:
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ continue;
++
++ default:
++ enter(StateType::Expression);
++ continue;
++ }
++ break;
++
++ case StateType::TernaryOp:
++ if (kind == QQmlJSGrammar::T_COLON) {
++ enter(StateType::TernaryOpAfterColon);
++ enter(StateType::ExpressionContinuation);
++ break;
++ }
++ Q_FALLTHROUGH();
++ case StateType::TernaryOpAfterColon:
++ case StateType::Expression:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_COMMA:
++ leave(true);
++ break;
++ case QQmlJSGrammar::T_RBRACKET:
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ continue;
++ case QQmlJSGrammar::T_RBRACE:
++ leave(true);
++ continue;
++ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
++ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
++ case QQmlJSGrammar::T_SEMICOLON:
++ leave(true);
++ break;
++ default:
++ if (Token::lexKindIsDelimiter(kind))
++ enter(StateType::ExpressionContinuation);
++ break;
++ }
++ break;
++
++ case StateType::ExpressionContinuation:
++ leave();
++ continue;
++
++ case StateType::ExpressionMaybeContinuation:
++ switch (kind) {
++ case QQmlJSGrammar::T_QUESTION:
++ case QQmlJSGrammar::T_LBRACKET:
++ case QQmlJSGrammar::T_LPAREN:
++ case QQmlJSGrammar::T_LBRACE:
++ leave();
++ continue;
++ default:
++ leave(!Token::lexKindIsDelimiter(kind));
++ continue;
++ }
++ break;
++
++ case StateType::ParenOpen:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ break;
++ }
++ break;
++
++ case StateType::BracketOpen:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_COMMA:
++ enter(StateType::BracketElementStart);
++ break;
++ case QQmlJSGrammar::T_RBRACKET:
++ leave();
++ break;
++ }
++ break;
++
++ case StateType::ObjectliteralOpen:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ enter(StateType::ObjectliteralAssignment);
++ break;
++ case QQmlJSGrammar::T_RBRACKET:
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ continue; // error recovery
++ case QQmlJSGrammar::T_RBRACE:
++ leave(true);
++ break;
++ }
++ break;
++
++ // pretty much like expression, but ends with , or }
++ case StateType::ObjectliteralAssignment:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_COMMA:
++ leave();
++ break;
++ case QQmlJSGrammar::T_RBRACKET:
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ continue; // error recovery
++ case QQmlJSGrammar::T_RBRACE:
++ leave();
++ continue; // so we also leave objectliteral_open
++ default:
++ if (Token::lexKindIsDelimiter(kind))
++ enter(StateType::ExpressionContinuation);
++ break;
++ }
++ break;
++
++ case StateType::BracketElementStart:
++ if (Token::lexKindIsIdentifier(kind)) {
++ turnInto(StateType::BracketElementMaybeObjectdefinition);
++ } else {
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::BracketElementMaybeObjectdefinition:
++ switch (kind) {
++ case QQmlJSGrammar::T_LBRACE:
++ turnInto(StateType::ObjectdefinitionOpen);
++ break;
++ default:
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::JsblockOpen:
++ case StateType::SubstatementOpen:
++ if (tryStatement())
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_RBRACE:
++ leave(true);
++ break;
++ }
++ break;
++
++ case StateType::LabelledStatement:
++ if (tryStatement())
++ break;
++ leave(true); // error recovery
++ break;
++
++ case StateType::Substatement:
++ // prefer substatement_open over block_open
++ if (kind != QQmlJSGrammar::T_LBRACE) {
++ if (tryStatement())
++ break;
++ }
++ switch (kind) {
++ case QQmlJSGrammar::T_LBRACE:
++ turnInto(StateType::SubstatementOpen);
++ break;
++ }
++ break;
++
++ case StateType::IfStatement:
++ switch (kind) {
++ case QQmlJSGrammar::T_LPAREN:
++ enter(StateType::ConditionOpen);
++ break;
++ default:
++ leave(true);
++ break; // error recovery
++ }
++ break;
++
++ case StateType::MaybeElse:
++ switch (kind) {
++ case QQmlJSGrammar::T_ELSE:
++ turnInto(StateType::ElseClause);
++ enter(StateType::Substatement);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::MaybeCatchOrFinally:
++ switch (kind) {
++ case QQmlJSGrammar::T_CATCH:
++ turnInto(StateType::CatchStatement);
++ break;
++ case QQmlJSGrammar::T_FINALLY:
++ turnInto(StateType::FinallyStatement);
++ break;
++ default:
++ leave(true);
++ continue;
++ }
++ break;
++
++ case StateType::ElseClause:
++ // ### shouldn't happen
++ dump();
++ Q_ASSERT(false);
++ leave(true);
++ break;
++
++ case StateType::ConditionOpen:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_RPAREN:
++ turnInto(StateType::Substatement);
++ break;
++ }
++ break;
++
++ case StateType::SwitchStatement:
++ case StateType::CatchStatement:
++ case StateType::StatementWithCondition:
++ switch (kind) {
++ case QQmlJSGrammar::T_LPAREN:
++ enter(StateType::StatementWithConditionParenOpen);
++ break;
++ default:
++ leave(true);
++ }
++ break;
++
++ case StateType::StatementWithConditionParenOpen:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_RPAREN:
++ turnInto(StateType::Substatement);
++ break;
++ }
++ break;
++
++ case StateType::TryStatement:
++ case StateType::FinallyStatement:
++ switch (kind) {
++ case QQmlJSGrammar::T_LBRACE:
++ enter(StateType::JsblockOpen);
++ break;
++ default:
++ leave(true);
++ break;
++ }
++ break;
++
++ case StateType::DoStatement:
++ switch (kind) {
++ case QQmlJSGrammar::T_WHILE:
++ break;
++ case QQmlJSGrammar::T_LPAREN:
++ enter(StateType::DoStatementWhileParenOpen);
++ break;
++ default:
++ leave(true);
++ continue; // error recovery
++ }
++ break;
++
++ case StateType::DoStatementWhileParenOpen:
++ if (tryInsideExpression(false))
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_RPAREN:
++ leave();
++ leave(true);
++ break;
++ }
++ break;
++
++ case StateType::BreakcontinueStatement:
++ if (Token ::lexKindIsIdentifier(kind)) {
++ leave(true);
++ } else {
++ leave(true);
++ continue; // try again
++ }
++ break;
++
++ case StateType::CaseStart:
++ switch (kind) {
++ case QQmlJSGrammar::T_COLON:
++ turnInto(StateType::CaseCont);
++ break;
++ }
++ break;
++
++ case StateType::CaseCont:
++ if (kind != QQmlJSGrammar::T_CASE && kind != QQmlJSGrammar::T_DEFAULT && tryStatement())
++ break;
++ switch (kind) {
++ case QQmlJSGrammar::T_RBRACE:
++ leave();
++ continue;
++ case QQmlJSGrammar::T_DEFAULT:
++ case QQmlJSGrammar::T_CASE:
++ leave();
++ continue;
++ }
++ break;
++
++ case StateType::MultilineCommentStart:
++ case StateType::MultilineCommentCont:
++ if (!Token::lexKindIsComment(kind)) {
++ leave();
++ continue;
++ } else if (tokenIndex == lineTokens.size() - 1
++ && !currentStatus.lexerState.isMultiline()) {
++ leave();
++ } else if (tokenIndex == 0) {
++ // to allow enter/leave to update the indentDepth
++ turnInto(StateType::MultilineCommentCont);
++ }
++ break;
++
++ default:
++ qWarning() << "Unhandled state" << currentStatus.state().typeStr();
++ break;
++ } // end of state switch
++
++ ++tokenIndex;
++ }
++
++ StateType topState = currentStatus.state().type;
++
++ // if there's no colon on the same line, it's not a label
++ if (topState == StateType::ExpressionOrLabel)
++ enterState(StateType::Expression);
++ // if not followed by an identifier on the same line, it's done
++ else if (topState == StateType::BreakcontinueStatement)
++ leaveState(true);
++
++ topState = currentStatus.state().type;
++
++ // some states might be continued on the next line
++ if (topState == StateType::Expression || topState == StateType::ExpressionOrObjectdefinition
++ || topState == StateType::ObjectliteralAssignment
++ || topState == StateType::TernaryOpAfterColon) {
++ enterState(StateType::ExpressionMaybeContinuation);
++ }
++ // multi-line comment start?
++ if (topState != StateType::MultilineCommentStart && topState != StateType::MultilineCommentCont
++ && currentStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_COMMENT) {
++ enterState(StateType::MultilineCommentStart);
++ }
++ currentStatus.finalIndent = currentIndent;
++ }
++
++ // adjusts the indentation of the current line based on the status of the previous one, and what
++ // it starts with
++ int indentForLineStartingWithToken(const FormatTextStatus &oldStatus, const FormatOptions &,
++ int tokenKind)
++ {
++ State topState = oldStatus.state();
++ State previousState = oldStatus.state(1);
++ int indentDepth = oldStatus.finalIndent;
++
++ // keep user-adjusted indent in multiline comments
++ if (topState.type == StateType::MultilineCommentStart
++ || topState.type == StateType::MultilineCommentCont) {
++ if (!Token::lexKindIsInvalid(tokenKind))
++ return -1;
++ }
++ // don't touch multi-line strings at all
++ if (oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL
++ || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL
++ || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD
++ || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE) {
++ return -1;
++ }
++
++ switch (tokenKind) {
++ case QQmlJSGrammar::T_LBRACE:
++ if (topState.type == StateType::Substatement
++ || topState.type == StateType::BindingAssignment
++ || topState.type == StateType::CaseCont) {
++ return topState.savedIndentDepth;
++ }
++ break;
++ case QQmlJSGrammar::T_RBRACE: {
++ if (topState.type == StateType::JsblockOpen && previousState.type == StateType::CaseCont) {
++ return previousState.savedIndentDepth;
++ }
++ for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
++ const StateType type = oldStatus.state(i).type;
++ if (type == StateType::ObjectdefinitionOpen || type == StateType::JsblockOpen
++ || type == StateType::SubstatementOpen || type == StateType::ObjectliteralOpen) {
++ return oldStatus.state(i).savedIndentDepth;
++ }
++ }
++ break;
++ }
++ case QQmlJSGrammar::T_RBRACKET:
++ for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
++ const StateType type = oldStatus.state(i).type;
++ if (type == StateType::BracketOpen) {
++ return oldStatus.state(i).savedIndentDepth;
++ }
++ }
++ break;
++ case QQmlJSGrammar::T_LBRACKET:
++ case QQmlJSGrammar::T_LPAREN:
++ if (topState.type == StateType::ExpressionMaybeContinuation)
++ return topState.savedIndentDepth;
++ break;
++ case QQmlJSGrammar::T_ELSE:
++ if (topState.type == StateType::MaybeElse) {
++ return oldStatus.state(1).savedIndentDepth;
++ } else if (topState.type == StateType::ExpressionMaybeContinuation) {
++ bool hasElse = false;
++ for (int i = 1; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
++ const StateType type = oldStatus.state(i).type;
++ if (type == StateType::ElseClause)
++ hasElse = true;
++ if (type == StateType::IfStatement) {
++ if (hasElse) {
++ hasElse = false;
++ } else {
++ return oldStatus.state(i).savedIndentDepth;
++ }
++ }
++ }
++ }
++ break;
++ case QQmlJSGrammar::T_CATCH:
++ case QQmlJSGrammar::T_FINALLY:
++ if (topState.type == StateType::MaybeCatchOrFinally)
++ return oldStatus.state(1).savedIndentDepth;
++ break;
++ case QQmlJSGrammar::T_COLON:
++ if (topState.type == StateType::TernaryOp)
++ return indentDepth - 2;
++ break;
++ case QQmlJSGrammar::T_QUESTION:
++ if (topState.type == StateType::ExpressionMaybeContinuation)
++ return topState.savedIndentDepth;
++ break;
++
++ case QQmlJSGrammar::T_DEFAULT:
++ case QQmlJSGrammar::T_CASE:
++ for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
++ const StateType type = oldStatus.state(i).type;
++ if (type == StateType::SwitchStatement || type == StateType::CaseCont) {
++ return oldStatus.state(i).savedIndentDepth;
++ } else if (type == StateType::TopmostIntro) {
++ break;
++ }
++ }
++ break;
++ default:
++ if (Token::lexKindIsDelimiter(tokenKind)
++ && topState.type == StateType::ExpressionMaybeContinuation)
++ return topState.savedIndentDepth;
++
++ break;
++ }
++ return indentDepth;
++ }
++
++ // sets currentIndent to the correct indent for the current line
++ int FormatPartialStatus::indentLine()
++ {
++ Q_ASSERT(currentStatus.size() >= 1);
++ int firstToken = (lineTokens.isEmpty() ? QQmlJSGrammar::T_NONE : tokenAt(0).lexKind);
++ int indent = indentForLineStartingWithToken(initialStatus, options, firstToken);
++ recalculateWithIndent(indent);
++ return indent;
++ }
++
++ int FormatPartialStatus::indentForNewLineAfter() const
++ {
++ // should be just currentIndent?
++ int indent = indentForLineStartingWithToken(currentStatus, options, QQmlJSGrammar::T_NONE);
++ if (indent < 0)
++ return currentIndent;
++ return indent;
++ }
++
++ void FormatPartialStatus::recalculateWithIndent(int indent)
++ {
++ if (indent >= 0) {
++ indentOffset = 0;
++ int i = 0;
++ while (i < line.size() && line.at(i).isSpace())
++ ++i;
++ indentOffset = indent - column(i);
++ }
++ currentIndent = initialStatus.finalIndent;
++ auto lexerState = currentStatus.lexerState;
++ currentStatus = initialStatus;
++ currentStatus.lexerState = lexerState;
++ tokenIndex = 0;
++ handleTokens();
++ }
++
++ FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options,
++ const FormatTextStatus &initialStatus)
++ {
++ FormatPartialStatus status(line, options, initialStatus);
++
++ status.handleTokens();
++
++ return status;
++ }
++
++ void FormatPartialStatus::defaultOnEnter(StateType newState, int *indentDepth,
++ int *savedIndentDepth) const
++ {
++ const State &parentState = currentStatus.state();
++ const Token &tk = tokenAt(tokenIndex);
++ const int tokenPosition = column(tk.begin());
++ const bool firstToken = (tokenIndex == 0);
++ const bool lastToken = (tokenIndex == lineTokens.size() - 1);
++
++ switch (newState) {
++ case StateType::ObjectdefinitionOpen: {
++ // special case for things like "gradient: Gradient {"
++ if (parentState.type == StateType::BindingAssignment)
++ *savedIndentDepth = currentStatus.state(1).savedIndentDepth;
++
++ if (firstToken)
++ *savedIndentDepth = tokenPosition;
++
++ *indentDepth = *savedIndentDepth + options.indentSize;
++ break;
++ }
++
++ case StateType::BindingOrObjectdefinition:
++ if (firstToken)
++ *indentDepth = *savedIndentDepth = tokenPosition;
++ break;
++
++ case StateType::BindingAssignment:
++ case StateType::ObjectliteralAssignment:
++ if (lastToken)
++ *indentDepth = *savedIndentDepth + options.indentSize;
++ else
++ *indentDepth = column(tokenAt(tokenIndex + 1).begin());
++ break;
++
++ case StateType::ExpressionOrObjectdefinition:
++ *indentDepth = tokenPosition;
++ break;
++
++ case StateType::ExpressionOrLabel:
++ if (*indentDepth == tokenPosition)
++ *indentDepth += 2 * options.indentSize;
++ else
++ *indentDepth = tokenPosition;
++ break;
++
++ case StateType::Expression:
++ if (*indentDepth == tokenPosition) {
++ // expression_or_objectdefinition doesn't want the indent
++ // expression_or_label already has it
++ if (parentState.type != StateType::ExpressionOrObjectdefinition
++ && parentState.type != StateType::ExpressionOrLabel
++ && parentState.type != StateType::BindingAssignment) {
++ *indentDepth += 2 * options.indentSize;
++ }
++ }
++ // expression_or_objectdefinition and expression_or_label have already consumed the
++ // first token
++ else if (parentState.type != StateType::ExpressionOrObjectdefinition
++ && parentState.type != StateType::ExpressionOrLabel) {
++ *indentDepth = tokenPosition;
++ }
++ break;
++
++ case StateType::ExpressionMaybeContinuation:
++ // set indent depth to indent we'd get if the expression ended here
++ for (int i = 1; currentStatus.state(i).type != StateType::TopmostIntro; ++i) {
++ const StateType type = currentStatus.state(i).type;
++ if (FormatTextStatus::isExpressionEndState(type)
++ && !FormatTextStatus::isBracelessState(type)) {
++ *indentDepth = currentStatus.state(i - 1).savedIndentDepth;
++ break;
++ }
++ }
++ break;
++
++ case StateType::BracketOpen:
++ if (parentState.type == StateType::Expression
++ && currentStatus.state(1).type == StateType::BindingAssignment) {
++ *savedIndentDepth = currentStatus.state(2).savedIndentDepth;
++ *indentDepth = *savedIndentDepth + options.indentSize;
++ } else if (parentState.type == StateType::ObjectliteralAssignment) {
++ *savedIndentDepth = parentState.savedIndentDepth;
++ *indentDepth = *savedIndentDepth + options.indentSize;
++ } else if (!lastToken) {
++ *indentDepth = tokenPosition + 1;
++ } else {
++ *indentDepth = *savedIndentDepth + options.indentSize;
++ }
++ break;
++
++ case StateType::LambdaStart:
++ case StateType::FunctionStart:
++ // align to the beginning of the line
++ *savedIndentDepth = *indentDepth = column(tokenAt(0).begin());
++ break;
++
++ case StateType::DoStatementWhileParenOpen:
++ case StateType::StatementWithConditionParenOpen:
++ case StateType::SignalArglistOpen:
++ case StateType::FunctionArglistOpen:
++ case StateType::ParenOpen:
++ if (!lastToken)
++ *indentDepth = tokenPosition + 1;
++ else
++ *indentDepth += options.indentSize;
++ break;
++
++ case StateType::TernaryOp:
++ if (!lastToken)
++ *indentDepth = tokenPosition + tk.length + 1;
++ else
++ *indentDepth += options.indentSize;
++ break;
++
++ case StateType::JsblockOpen:
++ // closing brace should be aligned to case
++ if (parentState.type == StateType::CaseCont) {
++ *savedIndentDepth = parentState.savedIndentDepth;
++ break;
++ }
++ Q_FALLTHROUGH();
++ case StateType::SubstatementOpen:
++ // special case for "foo: {" and "property int foo: {"
++ if (parentState.type == StateType::BindingAssignment)
++ *savedIndentDepth = currentStatus.state(1).savedIndentDepth;
++ *indentDepth = *savedIndentDepth + options.indentSize;
++ break;
++
++ case StateType::Substatement:
++ *indentDepth += options.indentSize;
++ break;
++
++ case StateType::ObjectliteralOpen:
++ if (parentState.type == StateType::Expression
++ || parentState.type == StateType::ObjectliteralAssignment) {
++ // undo the continuation indent of the expression
++ if (currentStatus.state(1).type == StateType::ExpressionOrLabel)
++ *indentDepth = currentStatus.state(1).savedIndentDepth;
++ else
++ *indentDepth = parentState.savedIndentDepth;
++ *savedIndentDepth = *indentDepth;
++ }
++ *indentDepth += options.indentSize;
++ break;
++
++ case StateType::StatementWithCondition:
++ case StateType::TryStatement:
++ case StateType::CatchStatement:
++ case StateType::FinallyStatement:
++ case StateType::IfStatement:
++ case StateType::DoStatement:
++ case StateType::SwitchStatement:
++ if (firstToken || parentState.type == StateType::BindingAssignment)
++ *savedIndentDepth = tokenPosition;
++ // ### continuation
++ *indentDepth = *savedIndentDepth; // + 2*options.indentSize;
++ // special case for 'else if'
++ if (!firstToken && newState == StateType::IfStatement
++ && parentState.type == StateType::Substatement
++ && currentStatus.state(1).type == StateType::ElseClause) {
++ *indentDepth = currentStatus.state(1).savedIndentDepth;
++ *savedIndentDepth = *indentDepth;
++ }
++ break;
++
++ case StateType::MaybeElse:
++ case StateType::MaybeCatchOrFinally: {
++ // set indent to where leave(true) would put it
++ int lastNonEndState = 0;
++ while (!FormatTextStatus::isExpressionEndState(
++ currentStatus.state(lastNonEndState + 1).type))
++ ++lastNonEndState;
++ *indentDepth = currentStatus.state(lastNonEndState).savedIndentDepth;
++ break;
++ }
++
++ case StateType::ConditionOpen:
++ // fixed extra indent when continuing 'if (', but not for 'else if ('
++ if (tokenPosition <= *indentDepth + options.indentSize)
++ *indentDepth += 2 * options.indentSize;
++ else
++ *indentDepth = tokenPosition + 1;
++ break;
++
++ case StateType::CaseStart:
++ *savedIndentDepth = tokenPosition;
++ break;
++
++ case StateType::CaseCont:
++ *indentDepth += options.indentSize;
++ break;
++
++ case StateType::MultilineCommentStart:
++ *indentDepth = tokenPosition + 2;
++ break;
++
++ case StateType::MultilineCommentCont:
++ *indentDepth = tokenPosition;
++ break;
++ default:
++ break;
++ }
++ }
++
++ } // namespace Dom
++ } // namespace QQmlJS
++ QT_END_NAMESPACE