diff options
-rw-r--r-- | src/plugins/cpptools/cppcodeformatter.cpp | 1067 | ||||
-rw-r--r-- | src/plugins/cpptools/cppcodeformatter.h | 195 | ||||
-rw-r--r-- | src/plugins/cpptools/cpptools.pro | 6 | ||||
-rw-r--r-- | src/plugins/texteditor/basetextdocumentlayout.cpp | 44 | ||||
-rw-r--r-- | src/plugins/texteditor/basetextdocumentlayout.h | 22 | ||||
-rw-r--r-- | tests/auto/cplusplus/codeformatter/codeformatter.pro | 19 | ||||
-rw-r--r-- | tests/auto/cplusplus/codeformatter/tst_codeformatter.cpp | 695 | ||||
-rw-r--r-- | tests/auto/cplusplus/cplusplus.pro | 2 |
8 files changed, 2043 insertions, 7 deletions
diff --git a/src/plugins/cpptools/cppcodeformatter.cpp b/src/plugins/cpptools/cppcodeformatter.cpp new file mode 100644 index 0000000000..8ab36724ff --- /dev/null +++ b/src/plugins/cpptools/cppcodeformatter.cpp @@ -0,0 +1,1067 @@ +#include "cppcodeformatter.h" + +#include <Token.h> +#include <Lexer.h> + +#include <texteditor/basetextdocumentlayout.h> + +#include <QtCore/QDebug> +#include <QtGui/QTextDocument> +#include <QtGui/QTextCursor> +#include <QtGui/QTextBlock> + +namespace CppTools { +namespace Internal { + class CppCodeFormatterData: public TextEditor::CodeFormatterData + { + public: + CppCodeFormatterData(int blockRevision, + const QStack<CodeFormatter::State> &beginState, + const QStack<CodeFormatter::State> &endState, + int indentDepth) + : CodeFormatterData(blockRevision) + , m_beginState(beginState) + , m_endState(endState) + , m_indentDepth(indentDepth) + {} + + QStack<CodeFormatter::State> m_beginState; + QStack<CodeFormatter::State> m_endState; + int m_indentDepth; + }; +} +} + +using namespace CPlusPlus; +using namespace CppTools; +using namespace TextEditor; +using namespace CppTools::Internal; + +CodeFormatter::CodeFormatter() + : m_document(0) + , m_indentDepth(0) +{ +} + +CodeFormatter::~CodeFormatter() +{ +} + +void CodeFormatter::setDocument(QTextDocument *document) +{ + m_document = document; +} + +void CodeFormatter::recalculateStateAfter(const QTextBlock &block) +{ + restoreBlockState(block.previous()); + + bool endedJoined = false; + const int lexerState = tokenizeBlock(block, &endedJoined); + m_tokenIndex = 0; + + if (tokenAt(0).kind() == T_POUND) { + enter(cpp_macro_start); + m_tokenIndex = 1; + } + + for (; m_tokenIndex < m_tokens.size(); ) { + m_currentToken = tokenAt(m_tokenIndex); + const int kind = m_currentToken.kind(); + + switch (m_currentState.top().type) { + case topmost_intro: + if (tryDeclaration()) + break; + switch (kind) { + case T_NAMESPACE: enter(namespace_start); break; + case T_STRUCT: + case T_UNION: + case T_CLASS: enter(class_start); break; + case T_ENUM: enter(enum_start); break; + case T_USING: enter(using_start); break; + } break; + + case namespace_start: + switch (kind) { + case T_LBRACE: turnInto(namespace_open); break; + } break; + + case namespace_open: + if (tryDeclaration()) + break; + switch (kind) { + case T_NAMESPACE: enter(namespace_start); break; + case T_RBRACE: leave(); break; + case T_STRUCT: + case T_UNION: + case T_CLASS: enter(class_start); break; + case T_ENUM: enter(enum_start); break; + case T_USING: enter(using_start); break; + } break; + + case class_start: + switch (kind) { + case T_SEMICOLON: leave(); break; + case T_LBRACE: turnInto(class_open); break; + } break; + + case class_open: + if (tryDeclaration()) + break; + switch (kind) { + case T_RBRACE: leave(); break; + case T_STRUCT: + case T_UNION: + case T_CLASS: enter(class_start); break; + case T_ENUM: enter(enum_start); break; + case T_USING: enter(using_start); break; + } break; + + case enum_start: + switch (kind) { + case T_SEMICOLON: leave(); break; + case T_LBRACE: turnInto(brace_list_open); break; + } break; + + case brace_list_open: + switch (kind) { + case T_RBRACE: leave(); break; + case T_LBRACE: enter(brace_list_open); break; // ### Other, nested brace list? + } break; + + case using_start: + switch (kind) { + case T_SEMICOLON: leave(); break; + } break; + + case template_start: + switch (kind) { + case T_LESS: turnInto(template_param); break; + } break; + + case template_param: + switch (kind) { + case T_LESS: enter(template_param); break; + case T_GREATER: leave(); break; + } break; + + case operator_declaration: + switch (kind) { + case T_LPAREN: break; + default: leave(); break; + } break; + + case declaration_start: + if (tryExpression(true)) + break; + switch (kind) { + case T_SEMICOLON: leave(true); break; + case T_EQUAL: enter(initializer); break; + case T_LBRACE: turnInto(defun_open); break; + case T_COLON: turnInto(member_init_open); break; + case T_OPERATOR: enter(operator_declaration); break; + } break; + + case initializer: + switch (kind) { + case T_LBRACE: enter(brace_list_open); break; + default: turnInto(expression); continue; + } break; + + case expression: + if (tryExpression()) + break; + switch (kind) { + case T_SEMICOLON: leave(); continue; + case T_LBRACE: + case T_COLON: + if (m_currentState.at(m_currentState.size() - 2).type == declaration_start) { + // oops, the expression was a function declaration argument list, hand lbrace/colon to declaration_start + leave(); + continue; + } + break; + } break; + + case arglist_open: + if (tryExpression()) + break; + switch (kind) { + case T_RPAREN: leave(); break; + } break; + + case ternary_op: + if (tryExpression()) + break; + switch (kind) { + case T_RPAREN: + case T_COMMA: + case T_SEMICOLON: leave(); continue; // always nested, propagate + } break; + + case stream_op: + if (tryExpression()) + break; + switch (kind) { + case T_COMMA: + case T_SEMICOLON: leave(); continue; // always nested, propagate semicolon + } break; + + case member_init_open: + switch (kind) { + case T_LBRACE: turnInto(defun_open); break; + case T_SEMICOLON: leave(); break; // ### so we don't break completely if it's a bitfield or ternary + } break; + + case defun_open: + if (tryStatement()) + break; + switch (kind) { + case T_RBRACE: leave(); break; + } break; + + case switch_statement: + case statement_with_condition: + case if_statement: + switch (kind) { + case T_LPAREN: enter(condition_open); break; + } break; + + case maybe_else: + if (m_currentToken.isComment()) { + break; + } else if (kind == T_ELSE) { + turnInto(else_clause); + enter(substatement); + break; + } else { + leave(true); + continue; + } + + case else_clause: + // ### shouldn't happen + dump(); + Q_ASSERT(false); + leave(true); + break; + + case do_statement: + // ### shouldn't happen + dump(); + Q_ASSERT(false); + leave(true); + break; + + case return_statement: + switch (kind) { + case T_SEMICOLON: leave(true); break; + } break; + + case substatement: + // prefer substatement_open over block_open + if (kind != T_LBRACE && tryStatement()) + break; + switch (kind) { + case T_LBRACE: turnInto(substatement_open); break; + case T_SEMICOLON: leave(true); break; + } break; + + case for_statement: + switch (kind) { + case T_LPAREN: enter(for_statement_paren_open); break; + } break; + + case for_statement_paren_open: + enter(for_statement_init); continue; + + case for_statement_init: + switch (kind) { + case T_SEMICOLON: turnInto(for_statement_condition); break; + case T_LPAREN: enter(condition_paren_open); break; + } break; + + case for_statement_condition: + switch (kind) { + case T_SEMICOLON: turnInto(for_statement_expression); break; + case T_LPAREN: enter(condition_paren_open); break; + } break; + + case for_statement_expression: + switch (kind) { + case T_RPAREN: leave(); turnInto(substatement); break; + case T_LPAREN: enter(condition_paren_open); break; + } break; + + case case_start: + switch (kind) { + case T_COLON: turnInto(case_cont); break; + } break; + + case case_cont: + if (kind != T_CASE && kind != T_DEFAULT && tryStatement()) + break; + switch (kind) { + case T_RBRACE: leave(); continue; + case T_DEFAULT: + case T_CASE: leave(); continue; + } break; + + case substatement_open: + if (tryStatement()) + break; + switch (kind) { + case T_RBRACE: leave(true); break; + } break; + + case condition_open: + switch (kind) { + case T_RPAREN: turnInto(substatement); break; + case T_LPAREN: enter(condition_paren_open); break; + } break; + + case block_open: + if (tryStatement()) + break; + switch(kind) { + case T_RBRACE: leave(true); break; + } break; + + // paren nesting + case condition_paren_open: + switch (kind) { + case T_RPAREN: leave(); break; + case T_LPAREN: enter(condition_paren_open); break; + } break; + + case qt_like_macro: + switch (kind) { + case T_LPAREN: enter(arglist_open); break; + case T_SEMICOLON: leave(true); break; + default: leave(); continue; + } break; + + case multiline_comment_start: + case multiline_comment_cont: + if (kind != T_COMMENT && kind != T_DOXY_COMMENT) { + leave(); + continue; + } else if (m_tokenIndex == m_tokens.size() - 1 + && lexerState == Lexer::State_Default) { + leave(); + } else if (m_tokenIndex == 0 && m_currentToken.isComment()) { + // to allow enter/leave to update the indentDepth + turnInto(multiline_comment_cont); + } + break; + + case cpp_macro_start: { + const int size = m_currentState.size(); + + int previousMarker = -1; + int previousPreviousMarker = -1; + for (int i = size - 1; i >= 0; --i) { + if (m_currentState.at(i).type == cpp_macro_conditional) { + if (previousMarker == -1) + previousMarker = i; + else { + previousPreviousMarker = i; + break; + } + } + } + + QStringRef tokenText = currentTokenText(); + if (tokenText == QLatin1String("ifdef") + || tokenText == QLatin1String("if") + || tokenText == QLatin1String("ifndef")) { + enter(cpp_macro_conditional); + // copy everything right of previousMarker, excluding cpp_macro_conditional + for (int i = previousMarker + 1; i < size; ++i) + m_currentState += m_currentState.at(i); + } + if (previousMarker != -1) { + if (tokenText == QLatin1String("endif")) { + QStack<State>::iterator begin = m_currentState.begin() + previousPreviousMarker + 1; + QStack<State>::iterator end = m_currentState.begin() + previousMarker + 1; + m_currentState.erase(begin, end); + } else if (tokenText == QLatin1String("else") + || tokenText == QLatin1String("elif")) { + m_currentState.resize(previousMarker + 1); + for (int i = previousPreviousMarker + 1; i < previousMarker; ++i) + m_currentState += m_currentState.at(i); + } + } + + turnInto(cpp_macro); + break; + } + + case cpp_macro: + case cpp_macro_cont: + break; + + default: + qWarning() << "Unhandled state" << m_currentState.top().type; + break; + + } // end of state switch + + ++m_tokenIndex; + } + + int topState = m_currentState.top().type; + + if (topState != multiline_comment_start + && topState != multiline_comment_cont + && (lexerState == Lexer::State_MultiLineComment + || lexerState == Lexer::State_MultiLineDoxyComment)) { + enter(multiline_comment_start); + } + + if (topState == qt_like_macro) + leave(true); + + if ((topState == cpp_macro_cont + || topState == cpp_macro) && !endedJoined) + leave(); + + if (topState == cpp_macro && endedJoined) + turnInto(cpp_macro_cont); + + storeBlockState(block); +} + +int CodeFormatter::indentFor(const QTextBlock &block) +{ +// qDebug() << "indenting for" << block.blockNumber() + 1; + + Q_ASSERT(m_document); + Q_ASSERT(m_document == block.document()); + + requireStatesUntil(block); + correctIndentation(block); + return m_indentDepth; +} + +void CodeFormatter::requireStatesUntil(const QTextBlock &endBlock) +{ + QStack<State> previousState = initialState(); + QTextBlock it = m_document->firstBlock(); + for (; it.isValid() && it != endBlock; it = it.next()) { + TextBlockUserData *userData = BaseTextDocumentLayout::userData(it); + CppCodeFormatterData *cppData = static_cast<CppCodeFormatterData *>(userData->codeFormatterData()); + if (!cppData) + break; + if (cppData->blockRevision() != it.revision()) + break; + if (previousState != cppData->m_beginState) + break; + if (TextBlockUserData::lexerState(it) == -1) + break; + + previousState = cppData->m_endState; + } + for (; it.isValid() && it != endBlock; it = it.next()) { + //qDebug() << "recalc line" << it.blockNumber() + 1; + recalculateStateAfter(it); + } + restoreBlockState(endBlock.previous()); +} + +CodeFormatter::State CodeFormatter::state(int belowTop) const +{ + if (belowTop < m_currentState.size()) + return m_currentState.at(m_currentState.size() - 1 - belowTop); + else + return State(); +} + +int CodeFormatter::tokenIndex() const +{ + return m_tokenIndex; +} + +int CodeFormatter::tokenIndexFromEnd() const +{ + return m_tokens.size() - 1 - m_tokenIndex; +} + +const CPlusPlus::Token &CodeFormatter::currentToken() const +{ + return m_currentToken; +} + +void CodeFormatter::invalidateCache() +{ + QTextBlock it = m_document->firstBlock(); + for (; it.isValid(); it = it.next()) { + TextBlockUserData *userData = BaseTextDocumentLayout::userData(it); + CppCodeFormatterData *cppData = static_cast<CppCodeFormatterData *>(userData->codeFormatterData()); + if (!cppData) + break; + cppData->setBlockRevision(-1); + } +} + +void CodeFormatter::enter(int newState) +{ + int savedIndentDepth = m_indentDepth; + onEnter(newState, &m_indentDepth, &savedIndentDepth); + m_currentState.push(State(newState, savedIndentDepth)); +} + +void CodeFormatter::leave(bool statementDone) +{ + Q_ASSERT(m_currentState.size() > 1); + if (m_currentState.top().type == topmost_intro) + return; + + // restore indent depth + State poppedState = m_currentState.pop(); + m_indentDepth = poppedState.savedIndentDepth; + + int topState = m_currentState.top().type; + + // does it suffice to check if token is T_SEMICOLON or T_RBRACE? + // maybe distinction between leave and turnInto? + if (statementDone) { + if (topState == substatement + || topState == statement_with_condition + || topState == for_statement + || topState == switch_statement + || topState == do_statement) { + leave(true); + } else if (topState == if_statement) { + if (poppedState.type != maybe_else) + enter(maybe_else); + else + leave(true); + } else if (topState == else_clause) { + // leave the else *and* the surrounding if, to prevent another else + leave(); + leave(true); + } + } +} + +void CodeFormatter::correctIndentation(const QTextBlock &block) +{ + const int lexerState = tokenizeBlock(block); + Q_ASSERT(m_currentState.size() >= 1); + + adjustIndent(m_tokens, lexerState, &m_indentDepth); +} + +bool CodeFormatter::tryExpression(bool alsoExpression) +{ + int newState = -1; + + const int kind = m_currentToken.kind(); + switch (kind) { + case T_LPAREN: newState = arglist_open; break; + case T_QUESTION: newState = ternary_op; break; + + case T_LESS_LESS: + case T_GREATER_GREATER: + // don't go into stream operator state inside arglist_open + // or another stream_op + newState = stream_op; + for (int i = m_currentState.size() - 1; i >= 0; --i) { + const int type = m_currentState.at(i).type; + if (type == arglist_open || type == stream_op) { + newState = -1; + break; + } + if (type == topmost_intro + || type == substatement_open + || type == defun_open + || type == namespace_open + || type == class_open + || type == brace_list_open) { + break; + } + } + break; + } + + if (newState != -1) { + if (alsoExpression) + enter(expression); + enter(newState); + return true; + } + + return false; +} + +bool CodeFormatter::tryDeclaration() +{ + const int kind = m_currentToken.kind(); + switch (kind) { + case T_Q_ENUMS: + case T_Q_PROPERTY: + case T_Q_FLAGS: + case T_Q_GADGET: + case T_Q_OBJECT: + case T_Q_INTERFACES: + case T_Q_DECLARE_INTERFACE: + case T_Q_PRIVATE_SLOT: + enter(qt_like_macro); + return true; + case T_IDENTIFIER: + if (m_tokenIndex == 0) { + QString tokenText = currentTokenText().toString(); + if (tokenText.startsWith(QLatin1String("Q_")) + || tokenText.startsWith(QLatin1String("QT_")) + || tokenText.startsWith(QLatin1String("QDOC_"))) { + enter(qt_like_macro); + return true; + } + } + // fallthrough + case T_CHAR: + case T_WCHAR_T: + case T_BOOL: + case T_SHORT: + case T_INT: + case T_LONG: + case T_SIGNED: + case T_UNSIGNED: + case T_FLOAT: + case T_DOUBLE: + case T_VOID: + case T_AUTO: + case T___TYPEOF__: + case T___ATTRIBUTE__: + case T_STATIC: + case T_FRIEND: + case T_CONST: + case T_VOLATILE: + case T_INLINE: + enter(declaration_start); + return true; + + case T_TEMPLATE: + enter(template_start); + return true; + + default: + return false; + } +} + +bool CodeFormatter::tryStatement() +{ + const int kind = m_currentToken.kind(); + if (tryDeclaration()) + return true; + switch (kind) { + case T_RETURN: + enter(return_statement); + enter(expression); + return true; + case T_FOR: + enter(for_statement); + return true; + case T_SWITCH: + enter(switch_statement); + return true; + case T_IF: + enter(if_statement); + return true; + case T_WHILE: + case T_Q_FOREACH: + enter(statement_with_condition); + return true; + case T_DO: + enter(do_statement); + enter(substatement); + return true; + case T_CASE: + case T_DEFAULT: + enter(case_start); + return true; + case T_LBRACE: + enter(block_open); + return true; + default: + return false; + } +} + +bool CodeFormatter::isBracelessState(int type) const +{ + return type == substatement + || type == if_statement + || type == else_clause + || type == statement_with_condition + || type == for_statement + || type == do_statement; +} + +const Token &CodeFormatter::tokenAt(int idx) const +{ + static const Token empty; + if (idx < 0 || idx >= m_tokens.size()) + return empty; + else + return m_tokens.at(idx); +} + +QStringRef CodeFormatter::currentTokenText() const +{ + return m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length()); +} + +void CodeFormatter::turnInto(int newState) +{ + leave(false); + enter(newState); +} + +void CodeFormatter::storeBlockState(const QTextBlock &block) +{ + if (!block.isValid()) + return; + + TextBlockUserData *userData = BaseTextDocumentLayout::userData(block); + userData->setCodeFormatterData( + new CppCodeFormatterData(block.revision(), m_beginState, m_currentState, m_indentDepth)); +} + +void CodeFormatter::restoreBlockState(const QTextBlock &block) +{ + if (block.isValid()) { + TextBlockUserData *userData = BaseTextDocumentLayout::userData(block); + CppCodeFormatterData *oldData = static_cast<CppCodeFormatterData *>(userData->codeFormatterData()); + if (oldData) { + m_currentState = oldData->m_endState; + m_beginState = m_currentState; + m_indentDepth = oldData->m_indentDepth; + return; + } + } + + m_currentState = initialState(); + m_beginState = m_currentState; + m_indentDepth = 0; +} + +QStack<CodeFormatter::State> CodeFormatter::initialState() +{ + static QStack<CodeFormatter::State> initialState; + if (initialState.isEmpty()) + initialState.push(State(topmost_intro, 0)); + return initialState; +} + +int CodeFormatter::tokenizeBlock(const QTextBlock &block, bool *endedJoined) +{ + int startState = TextBlockUserData::lexerState(block.previous()); + if (block.blockNumber() == 0) + startState = 0; + Q_ASSERT(startState != -1); + + SimpleLexer tokenize; + tokenize.setQtMocRunEnabled(true); + tokenize.setObjCEnabled(true); + + m_currentLine = block.text(); + // to determine whether a line was joined, Tokenizer needs a + // newline character at the end + m_currentLine.append(QLatin1Char('\n')); + m_tokens = tokenize(m_currentLine, startState); + + if (endedJoined) + *endedJoined = tokenize.endedJoined(); + + const int lexerState = tokenize.state(); + TextBlockUserData::setLexerState(block, lexerState); + return lexerState; +} + +void CodeFormatter::dump() +{ + qDebug() << "Current token index" << m_tokenIndex; + qDebug() << "Current state:"; + foreach (State s, m_currentState) { + qDebug() << s.type << s.savedIndentDepth; + } + qDebug() << "Current indent depth:" << m_indentDepth; +} + +QtStyleCodeFormatter::QtStyleCodeFormatter() + : m_indentSize(4) +{ +} + +void QtStyleCodeFormatter::setIndentSize(int size) +{ + if (size != m_indentSize) { + m_indentSize = size; + invalidateCache(); + } +} + +void QtStyleCodeFormatter::onEnter(int newState, int *indentDepth, int *savedIndentDepth) const +{ + const State &parentState = state(); + const Token &tk = currentToken(); + const int tokenPosition = tk.begin(); + const bool firstToken = (tokenIndex() == 0); + const bool lastToken = (tokenIndexFromEnd() == 0); + + switch (newState) { + case namespace_start: + if (firstToken) + *savedIndentDepth = tokenPosition; + *indentDepth = tokenPosition; + break; + + case enum_start: + case class_start: + if (firstToken) + *savedIndentDepth = tokenPosition; + *indentDepth = tokenPosition + 2*m_indentSize; + break; + + case template_param: + if (!lastToken) + *indentDepth = tokenPosition + tk.length(); + else + *indentDepth += 2*m_indentSize; + break; + + case statement_with_condition: + case for_statement: + case switch_statement: + case declaration_start: + case if_statement: + case return_statement: + if (firstToken) + *savedIndentDepth = tokenPosition; + *indentDepth = *savedIndentDepth + 2*m_indentSize; + break; + + case arglist_open: + case condition_paren_open: + if (!lastToken) + *indentDepth = tokenPosition + 1; + else + *indentDepth += m_indentSize; + break; + + case ternary_op: + if (!lastToken) + *indentDepth = tokenPosition + tk.length() + 1; + else + *indentDepth += m_indentSize; + break; + + case stream_op: + *indentDepth = tokenPosition + tk.length() + 1; + break; + + case member_init_open: + if (firstToken) + *indentDepth = tokenPosition + tk.length() + 1; + else + *indentDepth += m_indentSize; + break; + + case defun_open: + case class_open: + case case_cont: + *indentDepth += m_indentSize; + break; + + case substatement_open: + if (parentState.type != switch_statement) + *indentDepth += m_indentSize; + break; + + case brace_list_open: + if (parentState.type != initializer) + *indentDepth = parentState.savedIndentDepth + m_indentSize; + break; + + case block_open: + if (parentState.type != case_cont) + *indentDepth += m_indentSize; + break; + + case condition_open: + // undo the continuation indent of the parent + *indentDepth = parentState.savedIndentDepth; + *savedIndentDepth = *indentDepth; + + // fixed extra indent when continuing 'if (', but not for 'else if (' + if (tokenPosition <= *indentDepth + m_indentSize) + *indentDepth += 2*m_indentSize; + else + *indentDepth = tokenPosition + 1; + break; + + case substatement: + // undo the continuation indent of the parent + *indentDepth = parentState.savedIndentDepth; + *savedIndentDepth = *indentDepth; + break; + + case maybe_else: { + // set indent to outermost braceless savedIndent + int outermostBraceless = 0; + while (isBracelessState(state(outermostBraceless).type)) + ++outermostBraceless; + *indentDepth = state(outermostBraceless - 1).savedIndentDepth; + } break; + + case for_statement_paren_open: + *indentDepth = tokenPosition + 1; + break; + + case multiline_comment_start: + *indentDepth = tokenPosition + 2; + break; + + case multiline_comment_cont: + *indentDepth = tokenPosition; + break; + + case cpp_macro: + case cpp_macro_cont: + *indentDepth = m_indentSize; + break; + } +} + +void QtStyleCodeFormatter::adjustIndent(const QList<CPlusPlus::Token> &tokens, int lexerState, int *indentDepth) const +{ + State topState = state(); + State previousState = state(1); + + const bool topWasMaybeElse = (topState.type == maybe_else); + if (topWasMaybeElse) { + int outermostBraceless = 1; + while (state(outermostBraceless).type != invalid && isBracelessState(state(outermostBraceless).type)) + ++outermostBraceless; + + topState = state(outermostBraceless); + previousState = state(outermostBraceless + 1); + } + + + // adjusting the indentDepth here instead of in enter() gives 'else if' the correct indentation + // ### could be moved? + if (topState.type == substatement) + *indentDepth += m_indentSize; + + // keep user-adjusted indent in multiline comments + if (topState.type == multiline_comment_start + || topState.type == multiline_comment_cont) { + if (!tokens.isEmpty()) { + *indentDepth = tokens.at(0).begin(); + return; + } + } + + const int kind = tokenAt(0).kind(); + switch (kind) { + case T_POUND: *indentDepth = 0; break; + case T_COLON: + // ### ok for constructor initializer lists - what about ? and bitfields? + if (topState.type == expression && previousState.type == declaration_start) { + *indentDepth = previousState.savedIndentDepth + m_indentSize; + } else if (topState.type == ternary_op) { + *indentDepth -= 2; + } + break; + case T_COMMA: + if (topState.type == member_init_open) { + *indentDepth -= 2; + } + break; + case T_LBRACE: { + // function definition - argument list is expression state + if (topState.type == case_cont) + *indentDepth = topState.savedIndentDepth; + else if (topState.type == expression && previousState.type == declaration_start) + *indentDepth = previousState.savedIndentDepth; + else if (topState.type != defun_open + && topState.type != substatement_open + && topState.type != block_open + && !topWasMaybeElse) + *indentDepth = topState.savedIndentDepth; + break; + } + case T_RBRACE: { + if (topState.type == block_open && previousState.type == case_cont) { + *indentDepth = previousState.savedIndentDepth; + break; + } + for (int i = 0; state(i).type != topmost_intro; ++i) { + const int type = state(i).type; + if (type == defun_open + || type == substatement_open + || type == class_open + || type == brace_list_open + || type == namespace_open + || type == block_open) { + *indentDepth = state(i).savedIndentDepth; + break; + } + } + break; + } + case T_RPAREN: + if (topState.type == condition_open) { + *indentDepth = previousState.savedIndentDepth; + } + break; + case T_DEFAULT: + case T_CASE: + for (int i = 0; state(i).type != topmost_intro; ++i) { + const int type = state(i).type; + if (type == switch_statement || type == case_cont) { + *indentDepth = state(i).savedIndentDepth; + break; + } else if (type == topmost_intro) { + break; + } + } + break; + case T_PUBLIC: + case T_PRIVATE: + case T_PROTECTED: + case T_Q_SIGNALS: + if (topState.type == class_open) { + if (tokenAt(1).is(T_COLON) || tokenAt(2).is(T_COLON)) + *indentDepth = topState.savedIndentDepth; + } + break; + case T_ELSE: + if (topWasMaybeElse) + *indentDepth = state().savedIndentDepth; // topSavedIndent is actually the previous + break; + case T_LESS_LESS: + case T_GREATER_GREATER: + if (topState.type == stream_op) + *indentDepth -= 3; // to align << with << + break; + case T_COMMENT: + case T_DOXY_COMMENT: + case T_CPP_COMMENT: + case T_CPP_DOXY_COMMENT: + // unindent the last line of a comment + if ((topState.type == multiline_comment_cont + || topState.type == multiline_comment_start) + && (kind == T_COMMENT || kind == T_DOXY_COMMENT) + && (lexerState == Lexer::State_Default + || tokens.size() != 1)) { + *indentDepth -= m_indentSize; + } + break; + } +} diff --git a/src/plugins/cpptools/cppcodeformatter.h b/src/plugins/cpptools/cppcodeformatter.h new file mode 100644 index 0000000000..051b5f2aac --- /dev/null +++ b/src/plugins/cpptools/cppcodeformatter.h @@ -0,0 +1,195 @@ +#ifndef CPPCODEFORMATTER_H +#define CPPCODEFORMATTER_H + +#include "cpptools_global.h" + +#include <cplusplus/SimpleLexer.h> +#include <Token.h> + +#include <QtCore/QChar> +#include <QtCore/QStack> +#include <QtCore/QList> +#include <QtCore/QVector> +#include <QtCore/QPointer> + +QT_BEGIN_NAMESPACE +class QTextDocument; +class QTextBlock; +QT_END_NAMESPACE + +namespace CppTools { +namespace Internal { +class CppCodeFormatterData; +} + +class CPPTOOLS_EXPORT CodeFormatter +{ +public: + CodeFormatter(); + virtual ~CodeFormatter(); + + void setDocument(QTextDocument *document); + + int indentFor(const QTextBlock &block); + +protected: + virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const = 0; + virtual void adjustIndent(const QList<CPlusPlus::Token> &tokens, int lexerState, int *indentDepth) const = 0; + +protected: + enum StateType { + invalid = 0, + + topmost_intro, // The first line in a "topmost" definition. + + multiline_comment_start, // Inside the first line of a multi-line C style block comment. + multiline_comment_cont, // Inside the following lines of a multi-line C style block comment. + cpp_macro_start, // After the '#' token + cpp_macro, // The start of a C preprocessor macro definition. + cpp_macro_cont, // Subsequent lines of a multi-line C preprocessor macro definition. + cpp_macro_conditional, // Special marker used for separating saved from current state when dealing with #ifdef + qt_like_macro, // after an identifier starting with Q_ or QT_ at the beginning of the line + + defun_open, // Brace that opens a top-level function definition. + using_start, // right after the "using" token + + class_start, // after the 'class' token + class_open, // Brace that opens a class definition. + member_init_open, // After ':' that starts a member initialization list. + + enum_start, // After 'enum' + brace_list_open, // Open brace of an enum or static array list. + + namespace_start, // after the namespace token, before the opening brace. + namespace_open, // Brace that opens a C++ namespace block. + + declaration_start, // shifted a token which could start a declaration. + operator_declaration, // after 'operator' in declaration_start + + template_start, // after the 'template' token + template_param, // after the '<' in a template_start + + if_statement, // After 'if' + maybe_else, // after the first substatement in an if + else_clause, // The else line of an if-else construct. + + for_statement, // After the 'for' token + for_statement_paren_open, // While inside the (...) + for_statement_init, // The initializer part of the for statement + for_statement_condition, // The condition part of the for statement + for_statement_expression, // The expression part of the for statement + + switch_statement, // After 'switch' token + case_start, // after a 'case' or 'default' token + case_cont, // after the colon in a case/default + + statement_with_condition, // A statement that takes a condition after the start token. + do_statement, // After 'do' token + return_statement, // After 'return' + block_open, // Statement block open brace. + + substatement, // The first line after a conditional or loop construct. + substatement_open, // The brace that opens a substatement block. + + arglist_open, // after the lparen. TODO: check if this is enough. + stream_op, // Lines continuing a stream operator (C++ only). + ternary_op, // The ? : operator + + condition_open, // Start of a condition in 'if', 'while', entered after opening paren + condition_paren_open, // After an lparen in a condition + + assign_open, // after an assignment token + + expression, // after a '=' in a declaration_start once we're sure it's not '= {' + initializer, // after a '=' in a declaration start + }; + + struct State { + State() + : savedIndentDepth(0) + , type(0) + {} + + State(quint8 ty, quint16 savedDepth) + : savedIndentDepth(savedDepth) + , type(ty) + {} + + quint16 savedIndentDepth; + quint8 type; + + bool operator==(const State &other) const { + return type == other.type + && savedIndentDepth == other.savedIndentDepth; + } + }; + + State state(int belowTop = 0) const; + int tokenIndex() const; + int tokenIndexFromEnd() const; + const CPlusPlus::Token ¤tToken() const; + const CPlusPlus::Token &tokenAt(int idx) const; + + bool isBracelessState(int type) const; + + void invalidateCache(); + +private: + void requireStatesUntil(const QTextBlock &block); + void recalculateStateAfter(const QTextBlock &block); + void storeBlockState(const QTextBlock &block); + void restoreBlockState(const QTextBlock &block); + + QStringRef currentTokenText() const; + + int tokenizeBlock(const QTextBlock &block, bool *endedJoined = 0); + + void turnInto(int newState); + + bool tryExpression(bool alsoExpression = false); + bool tryDeclaration(); + bool tryStatement(); + + void enter(int newState); + void leave(bool statementDone = false); + void correctIndentation(const QTextBlock &block); + + void dump(); + +private: + static QStack<State> initialState(); + + QPointer<QTextDocument> m_document; + + QStack<State> m_beginState; + QStack<State> m_currentState; + + QList<CPlusPlus::Token> m_tokens; + QString m_currentLine; + CPlusPlus::Token m_currentToken; + int m_tokenIndex; + + // should store indent level and padding instead + int m_indentDepth; + + friend class Internal::CppCodeFormatterData; +}; + +class CPPTOOLS_EXPORT QtStyleCodeFormatter : public CodeFormatter +{ +public: + QtStyleCodeFormatter(); + + void setIndentSize(int size); + +protected: + virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const; + virtual void adjustIndent(const QList<CPlusPlus::Token> &tokens, int lexerState, int *indentDepth) const; + +private: + int m_indentSize; +}; + +} // namespace CppTools + +#endif // CPPCODEFORMATTER_H diff --git a/src/plugins/cpptools/cpptools.pro b/src/plugins/cpptools/cpptools.pro index de7adcd66f..6ab11b6058 100644 --- a/src/plugins/cpptools/cpptools.pro +++ b/src/plugins/cpptools/cpptools.pro @@ -23,7 +23,8 @@ HEADERS += completionsettingspage.h \ searchsymbols.h \ cppdoxygen.h \ cppfilesettingspage.h \ - cppfindreferences.h + cppfindreferences.h \ + cppcodeformatter.h SOURCES += completionsettingspage.cpp \ cppclassesfilter.cpp \ @@ -38,7 +39,8 @@ SOURCES += completionsettingspage.cpp \ cppdoxygen.cpp \ cppfilesettingspage.cpp \ abstracteditorsupport.cpp \ - cppfindreferences.cpp + cppfindreferences.cpp \ + cppcodeformatter.cpp FORMS += completionsettingspage.ui \ cppfilesettingspage.ui diff --git a/src/plugins/texteditor/basetextdocumentlayout.cpp b/src/plugins/texteditor/basetextdocumentlayout.cpp index 58427566bb..871cab0e13 100644 --- a/src/plugins/texteditor/basetextdocumentlayout.cpp +++ b/src/plugins/texteditor/basetextdocumentlayout.cpp @@ -31,6 +31,15 @@ using namespace TextEditor; +CodeFormatterData::CodeFormatterData(int blockRevision) + : m_blockRevision(blockRevision) +{ +} + +CodeFormatterData::~CodeFormatterData() +{ +} + TextBlockUserData::~TextBlockUserData() { TextMarks marks = m_marks; @@ -38,6 +47,9 @@ TextBlockUserData::~TextBlockUserData() foreach (ITextMark *mrk, marks) { mrk->removedFromEditor(); } + + if (m_codeFormatterData) + delete m_codeFormatterData; } int TextBlockUserData::braceDepthDelta() const @@ -359,6 +371,35 @@ TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor * return NoMatch; } +int TextBlockUserData::lexerState(const QTextBlock &block) +{ + if (!block.isValid()) + return -1; + + int data = block.userState(); + if (data == -1) + return -1; + return data & 0xFF; +} + +void TextBlockUserData::setLexerState(QTextBlock block, int state) +{ + if (!block.isValid()) + return; + + int data = block.userState(); + if (data == -1) + data = 0; + block.setUserState((data & ~0xFF) | (state & 0xFF)); +} + +void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data) +{ + if (m_codeFormatterData) + delete m_codeFormatterData; + + m_codeFormatterData = data; +} BaseTextDocumentLayout::BaseTextDocumentLayout(QTextDocument *doc) :QPlainTextDocumentLayout(doc) { @@ -528,6 +569,3 @@ QSizeF BaseTextDocumentLayout::documentSize() const size.setWidth(qMax((qreal)m_requiredWidth, size.width())); return size; } - - - diff --git a/src/plugins/texteditor/basetextdocumentlayout.h b/src/plugins/texteditor/basetextdocumentlayout.h index b3b20ce2d4..76323c6384 100644 --- a/src/plugins/texteditor/basetextdocumentlayout.h +++ b/src/plugins/texteditor/basetextdocumentlayout.h @@ -54,6 +54,18 @@ struct TEXTEDITOR_EXPORT Parenthesis int pos; }; +class TEXTEDITOR_EXPORT CodeFormatterData +{ +public: + CodeFormatterData(int blockRevision); + virtual ~CodeFormatterData(); + + int blockRevision() const { return m_blockRevision; } + void setBlockRevision(int revision) { m_blockRevision = revision; } + +private: + int m_blockRevision; +}; class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData { @@ -64,7 +76,9 @@ public: m_ifdefedOut(false), m_foldingIndent(0), m_foldingStartIncluded(false), - m_foldingEndIncluded(false){} + m_foldingEndIncluded(false), + m_codeFormatterData(0) + {} ~TextBlockUserData(); inline TextMarks marks() const { return m_marks; } @@ -106,6 +120,11 @@ public: void setFoldingEndIncluded(bool included) { m_foldingEndIncluded = included; } bool foldingEndIncluded() const { return m_foldingEndIncluded; } + static int lexerState(const QTextBlock &block); + static void setLexerState(QTextBlock block, int state); + + CodeFormatterData *codeFormatterData() const { return m_codeFormatterData; } + void setCodeFormatterData(CodeFormatterData *data); private: TextMarks m_marks; @@ -115,6 +134,7 @@ private: uint m_foldingStartIncluded : 1; uint m_foldingEndIncluded : 1; Parentheses m_parentheses; + CodeFormatterData *m_codeFormatterData; }; diff --git a/tests/auto/cplusplus/codeformatter/codeformatter.pro b/tests/auto/cplusplus/codeformatter/codeformatter.pro new file mode 100644 index 0000000000..de80180f9f --- /dev/null +++ b/tests/auto/cplusplus/codeformatter/codeformatter.pro @@ -0,0 +1,19 @@ +TEMPLATE = app +CONFIG += qt warn_on console depend_includepath +QT += testlib + +include(../shared/shared.pri) + +SRCDIR = ../../../../src + +SOURCES += \ + tst_codeformatter.cpp \ + $$SRCDIR/plugins/cpptools/cppcodeformatter.cpp \ + $$SRCDIR/plugins/texteditor/basetextdocumentlayout.cpp + +HEADERS += \ + $$SRCDIR/plugins/texteditor/basetextdocumentlayout.h + +INCLUDEPATH += $$SRCDIR/plugins $$SRCDIR/libs + +TARGET=tst_$$TARGET diff --git a/tests/auto/cplusplus/codeformatter/tst_codeformatter.cpp b/tests/auto/cplusplus/codeformatter/tst_codeformatter.cpp new file mode 100644 index 0000000000..7082c21b94 --- /dev/null +++ b/tests/auto/cplusplus/codeformatter/tst_codeformatter.cpp @@ -0,0 +1,695 @@ +#include <QtTest> +#include <QObject> +#include <QList> +#include <QTextDocument> +#include <QTextBlock> + +#include <cpptools/cppcodeformatter.h> +using namespace CppTools; + +class tst_CodeFormatter: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void ifStatementWithoutBraces1(); + void ifStatementWithoutBraces2(); + void ifStatementWithBraces1(); + void ifStatementWithBraces2(); + void ifStatementMixed(); + void ifStatementAndComments(); + void ifStatementLongCondition(); + void strayElse(); + void macrosNoSemicolon(); + void oneLineIf(); + void doWhile(); + void closingCurlies(); + void ifdefedInsideIf(); + void ifdefs(); + void preprocessorContinuation(); + void cStyleComments(); + void cppStyleComments(); + void expressionContinuation(); + void classAccess(); + void ternary(); + void objcAtDeclarations(); + void braceList(); + void bug1(); + void bug2(); + void switch1(); + void memberInitializer(); + void templates(); + void operatorOverloads(); +}; + +struct Line { + Line(QString l) + : line(l) + { + for (int i = 0; i < l.size(); ++i) { + if (!l.at(i).isSpace()) { + expectedIndent = i; + return; + } + } + expectedIndent = l.size(); + } + + Line(QString l, int expect) + : line(l), expectedIndent(expect) + {} + + QString line; + int expectedIndent; +}; + +QString concatLines(QList<Line> lines) +{ + QString result; + foreach (const Line &l, lines) { + result += l.line; + result += "\n"; + } + return result; +} + +void checkIndent(QList<Line> data) +{ + QString text = concatLines(data); + QTextDocument document(text); + QtStyleCodeFormatter formatter; + formatter.setDocument(&document); + + int i = 0; + foreach (const Line &l, data) { + if (l.expectedIndent != -1) { + int actualIndent = formatter.indentFor(document.findBlockByLineNumber(i)); + if (actualIndent != l.expectedIndent) { + QFAIL(QString("Wrong indent in line %1 with text '%2', expected indent %3, got %4").arg( + QString::number(i+1), l.line, QString::number(l.expectedIndent), QString::number(actualIndent)).toLatin1().constData()); + } + } + ++i; + } +} + +void tst_CodeFormatter::ifStatementWithoutBraces1() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" if (a)") + << Line(" if (b)") + << Line(" foo;") + << Line(" else if (c)") + << Line(" foo;") + << Line(" else") + << Line(" if (d)") + << Line(" foo;") + << Line(" else") + << Line(" while (e)") + << Line(" bar;") + << Line(" else") + << Line(" foo;") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::ifStatementWithoutBraces2() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" if (a)") + << Line(" if (b)") + << Line(" foo;") + << Line(" if (a) b();") + << Line(" if (a) b(); else") + << Line(" foo;") + << Line(" if (a)") + << Line(" if (b)") + << Line(" foo;") + << Line(" else if (c)") + << Line(" foo;") + << Line(" else") + << Line(" if (d)") + << Line(" foo;") + << Line(" else") + << Line(" while (e)") + << Line(" bar;") + << Line(" else") + << Line(" foo;") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::ifStatementWithBraces1() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" if (a) {") + << Line(" if (b) {") + << Line(" foo;") + << Line(" } else if (c) {") + << Line(" foo;") + << Line(" } else {") + << Line(" if (d) {") + << Line(" foo;") + << Line(" } else {") + << Line(" foo;") + << Line(" }") + << Line(" }") + << Line(" } else {") + << Line(" foo;") + << Line(" }") + << Line("}"); + checkIndent(data); +} + +void tst_CodeFormatter::ifStatementWithBraces2() +{ + QList<Line> data; + data << Line("void foo()") + << Line("{") + << Line(" if (a)") + << Line(" {") + << Line(" if (b)") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line(" else if (c)") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line(" else") + << Line(" {") + << Line(" if (d)") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line(" else") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line(" }") + << Line(" }") + << Line(" else") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line("}"); + checkIndent(data); +} + +void tst_CodeFormatter::ifStatementMixed() +{ + QList<Line> data; + data << Line("void foo()") + << Line("{") + << Line(" if (foo)") + << Line(" if (bar)") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line(" else") + << Line(" if (car)") + << Line(" {}") + << Line(" else doo;") + << Line(" else abc;") + << Line("}"); + checkIndent(data); +} + +void tst_CodeFormatter::ifStatementAndComments() +{ + QList<Line> data; + data << Line("void foo()") + << Line("{") + << Line(" if (foo)") + << Line(" ; // bla") + << Line(" else if (bar)") + << Line(" ;") + << Line(" if (foo)") + << Line(" ; /* bla") + << Line(" bla */") + << Line(" else if (bar)") + << Line(" // foobar") + << Line(" ;") + << Line(" else if (bar)") + << Line(" /* bla") + << Line(" bla */") + << Line(" ;") + << Line("}"); + checkIndent(data); +} + +void tst_CodeFormatter::ifStatementLongCondition() +{ + QList<Line> data; + data << Line("void foo()") + << Line("{") + << Line(" if (foo &&") + << Line(" bar") + << Line(" || (a + b > 4") + << Line(" && foo(bar)") + << Line(" )") + << Line(" ) {") + << Line(" foo;") + << Line(" }") + << Line("}"); + checkIndent(data); +} + +void tst_CodeFormatter::strayElse() +{ + QList<Line> data; + data << Line("void foo()") + << Line("{") + << Line(" while( true ) {}") + << Line(" else", -1) + << Line(" else {", -1) + << Line(" }", -1) + << Line("}"); + checkIndent(data); +} + +void tst_CodeFormatter::macrosNoSemicolon() +{ + QList<Line> data; + data << Line("QT_DECLARE_METATYPE(foo)") + << Line("int i;") + << Line("Q_BLABLA") + << Line("int i;") + << Line("Q_BLABLA;") + << Line("int i;") + << Line("Q_BLABLA();") + << Line("int i;") + << Line("Q_PROPERTY(abc)") + << Line("QDOC_PROPERTY(abc)") + << Line("void foo() {") + << Line(" QT_DECLARE_METATYPE(foo)") + << Line(" int i;") + << Line(" Q_BLABLA") + << Line(" int i;") + << Line(" Q_BLABLA;") + << Line(" int i;") + << Line(" Q_BLABLA();") + << Line(" Q_PROPERTY(abc)") + << Line(" QDOC_PROPERTY(abc)") + << Line(" int i;") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::oneLineIf() +{ + QList<Line> data; + data << Line("class F {") + << Line(" void foo()") + << Line(" { if (showIt) show(); }") + << Line("};") + ; + checkIndent(data); + +} + +void tst_CodeFormatter::doWhile() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" do { if (c) foo; } while(a);") + << Line(" do {") + << Line(" if(a);") + << Line(" } while(a);") + << Line(" do") + << Line(" foo;") + << Line(" while(a);") + << Line(" do foo; while(a);") + << Line("};") + ; + checkIndent(data); +} + +void tst_CodeFormatter::closingCurlies() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" if (a)") + << Line(" if (b) {") + << Line(" foo;") + << Line(" }") + << Line(" {") + << Line(" foo();") + << Line(" }") + << Line(" foo();") + << Line(" {") + << Line(" foo();") + << Line(" }") + << Line(" while (a) {") + << Line(" if (a);") + << Line(" }") + << Line("};") + ; + checkIndent(data); +} + +void tst_CodeFormatter::ifdefedInsideIf() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" if (a) {") + << Line("#ifndef Q_WS_WINCE") + << Line(" if (b) {") + << Line("#else") + << Line(" if (c) {") + << Line("#endif") + << Line(" }") + << Line(" } else if (d) {") + << Line(" }") + << Line(" if (a)") + << Line(" ;") + << Line(" else if (type == Qt::Dialog || type == Qt::Sheet)") + << Line("#ifndef Q_WS_WINCE") + << Line(" flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowContextHelpButtonHint | Qt::WindowCloseButtonHint;") + << Line("#else") + << Line(" bar;") + << Line("#endif") + << Line("};") + ; + checkIndent(data); +} + +void tst_CodeFormatter::ifdefs() +{ + QList<Line> data; + data << Line("#ifdef FOO") + << Line("#include <bar>") + << Line("void foo()") + << Line("{") + << Line(" if (bar)") + << Line("#if 1") + << Line(" foo;") + << Line(" else") + << Line("#endif") + << Line(" foo;") + << Line("}") + << Line("#endif") + ; + checkIndent(data); +} + +void tst_CodeFormatter::preprocessorContinuation() +{ + QList<Line> data; + data << Line("#define x \\") + << Line(" foo(x) + \\") + << Line(" bar(x)") + << Line("int i;") + << Line("void foo() {") + << Line("#define y y") + << Line("#define x \\") + << Line(" foo(x) + \\") + << Line(" bar(x)") + << Line(" int j;") + << Line("};") + ; + checkIndent(data); +} + +void tst_CodeFormatter::cStyleComments() +{ + QList<Line> data; + data << Line("/*") + << Line(" ") + << Line(" foo") + << Line(" ") + << Line(" foo") + << Line(" ") + << Line("*/") + << Line("void foo() {") + << Line(" /*") + << Line(" ") + << Line(" foo") + << Line(" ") + << Line(" */") + << Line(" /* bar */") + << Line("}") + << Line("struct s {") + << Line(" /* foo */") + << Line(" /*") + << Line(" ") + << Line(" foo") + << Line(" ") + << Line(" */") + << Line(" /* bar */") + ; + checkIndent(data); +} + +void tst_CodeFormatter::cppStyleComments() +{ + QList<Line> data; + data << Line("// abc") + << Line("class C { ") + << Line(" // ghij") + << Line(" // def") + << Line(" // ghij") + << Line(" int i; // hik") + << Line(" // doo") + << Line("} // ba") + << Line("// ba") + ; + checkIndent(data); +} + +void tst_CodeFormatter::expressionContinuation() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" return (a <= b &&") + << Line(" c <= d);") + << Line(" return (qMax <= qMin() &&") + << Line(" qMax(r1.top(), r2.top()) <= qMin(r1.bottom(), r2.bottom()));") + << Line(" return a") + << Line(" == b && c == d;") + << Line(" return a ==") + << Line(" b && c == d;") + << Line(" return a == b") + << Line(" && c == d;") + << Line(" return a == b &&") + << Line(" c == d;") + << Line(" return a == b && c") + << Line(" == d;") + << Line(" return a == b && c ==") + << Line(" d;") + << Line(" return a == b && c == d;") + << Line(" qDebug() << foo") + << Line(" << bar << moose") + << Line(" << bar +") + << Line(" foo - blah(1)") + << Line(" << '?'") + << Line(" << \"\\n\";") + << Line(" i += foo(") + << Line(" bar,") + << Line(" 2);") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::classAccess() +{ + QList<Line> data; + data << Line("class foo {") + << Line(" int i;") + << Line("public:") + << Line(" class bar {") + << Line(" private:") + << Line(" int i;") + << Line(" public:") + << Line(" private slots:") + << Line(" void foo();") + << Line(" public Q_SLOTS:") + << Line(" Q_SIGNALS:") + << Line(" };") + << Line(" float f;") + << Line("private:") + << Line(" void bar(){}") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::ternary() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" int i = a ? b : c;") + << Line(" foo += a_bigger_condition ?") + << Line(" b") + << Line(" : c;") + << Line(" int i = a ?") + << Line(" b : c;") + << Line(" int i = a ? b") + << Line(" : c +") + << Line(" 2;") + << Line(" int i = (a ? b : c) + (foo") + << Line(" bar);") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::bug1() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" if (attribute < int(8*sizeof(uint))) {") + << Line(" if (on)") + << Line(" data->widget_attributes |= (1<<attribute);") + << Line(" else") + << Line(" data->widget_attributes &= ~(1<<attribute);") + << Line(" } else {") + << Line(" const int x = attribute - 8*sizeof(uint);") + << Line(" }") + << Line(" int i;") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::bug2() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" const int sourceY = foo(") + << Line(" bar(") + << Line(" car(a,") + << Line(" b),") + << Line(" b),") + << Line(" foo);") + << Line(" const int sourceY =") + << Line(" foo(") + << Line(" bar(a,") + << Line(" b),") + << Line(" b);") + << Line(" int j;") + << Line(" const int sourceY =") + << Line(" (direction == DirectionEast || direction == DirectionWest) ?") + << Line(" (sourceRect.top() + (sourceRect.bottom() - sourceRect.top()) / 2)") + << Line(" : (direction == DirectionSouth ? sourceRect.bottom() : sourceRect.top());") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::braceList() +{ + QList<Line> data; + data << Line("enum Foo {") + << Line(" a,") + << Line(" b,") + << Line("};") + << Line("void foo () {") + << Line(" int[] a = { foo, bar, ") + << Line(" car };") + << Line(" int k;") + ; + checkIndent(data); + +} + +void tst_CodeFormatter::objcAtDeclarations() +{ + QList<Line> data; + data << Line("@class Forwarded;") + << Line("@protocol Forwarded;") + << Line("int i;") + ; + checkIndent(data); +} + +void tst_CodeFormatter::switch1() +{ + QList<Line> data; + data << Line("void foo() {") + << Line(" switch (a) {") + << Line(" case 1:") + << Line(" foo;") + << Line(" if (a);") + << Line(" case 2:") + << Line(" case 3: {") + << Line(" foo;") + << Line(" }") + << Line(" case 4:") + << Line(" {") + << Line(" foo;") + << Line(" }") + << Line(" case bar:") + << Line(" break;") + << Line(" }") + << Line(" case 4:") + << Line(" {") + << Line(" if (a) {") + << Line(" }") + << Line(" }") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::memberInitializer() +{ + QList<Line> data; + data << Line("void foo()") + << Line(" : baR()") + << Line(" , m_member(23)") + << Line("{") + << Line("}") + << Line("class Foo {") + << Line(" Foo()") + << Line(" : baR(),") + << Line(" moo(barR)") + << Line(" {}") + << Line("}") + ; + checkIndent(data); +} + +void tst_CodeFormatter::templates() +{ + QList<Line> data; + data << Line("template <class T, typename F, int i>") + << Line("class Foo {") + << Line("private:") + << Line("};") + << Line("template <class T,") + << Line(" typename F, int i") + << Line(" >") + << Line("class Foo {") + << Line("private:") + << Line("};") + ; + checkIndent(data); +} + +void tst_CodeFormatter::operatorOverloads() +{ + QList<Line> data; + data << Line("struct S {") + << Line(" void operator()() {") + << Line(" foo;") + << Line(" }") + << Line(" void operator<<() {") + << Line(" foo;") + << Line(" }") + << Line("};") + ; + checkIndent(data); +} + +QTEST_APPLESS_MAIN(tst_CodeFormatter) +#include "tst_codeformatter.moc" + + diff --git a/tests/auto/cplusplus/cplusplus.pro b/tests/auto/cplusplus/cplusplus.pro index 5b80ab98e0..947a140b4e 100644 --- a/tests/auto/cplusplus/cplusplus.pro +++ b/tests/auto/cplusplus/cplusplus.pro @@ -1,3 +1,3 @@ TEMPLATE = subdirs -SUBDIRS = shared ast semantic lookup preprocessor findusages typeprettyprinter +SUBDIRS = shared ast semantic lookup preprocessor findusages typeprettyprinter codeformatter CONFIG += ordered |