Revision control

Copy as Markdown

// 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, &currentIndent, &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