diff options
Diffstat (limited to 'src/qmldom/qqmldomcodeformatter.cpp')
-rw-r--r-- | src/qmldom/qqmldomcodeformatter.cpp | 1409 |
1 files changed, 1409 insertions, 0 deletions
diff --git a/src/qmldom/qqmldomcodeformatter.cpp b/src/qmldom/qqmldomcodeformatter.cpp new file mode 100644 index 0000000000..5660025fd8 --- /dev/null +++ b/src/qmldom/qqmldomcodeformatter.cpp @@ -0,0 +1,1409 @@ +// 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 + +#include "qqmldomcodeformatter_p.h" + +#include <QLoggingCategory> +#include <QMetaEnum> + +static Q_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_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_FUNCTION_STAR: + 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_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_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::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: + case QQmlJSGrammar::T_FUNCTION_STAR: + 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: + case QQmlJSGrammar::T_IDENTIFIER: + enter(StateType::PropertyName); + break; + default: + if (Token::lexKindIsIdentifier(kind) && tokenText(currentToken) == u"list") { + enter(StateType::PropertyListOpen); + } 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::TypeAnnotation: + switch (kind) { + case QQmlJSGrammar::T_IDENTIFIER: + 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::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 |