QmlJS: Introduce a new indenter that works similarly to the new C++ one.

Done-with: Thomas Hartmann
This commit is contained in:
Christian Kamm
2010-07-07 11:45:18 +02:00
parent f6232260c2
commit 822de6c17a
10 changed files with 2587 additions and 12 deletions

View File

@@ -56,6 +56,11 @@ OTHER_FILES += \
$$PWD/parser/qmljs.g $$PWD/parser/qmljs.g
contains(QT, gui) { contains(QT, gui) {
SOURCES += $$PWD/qmljsindenter.cpp SOURCES += \
HEADERS += $$PWD/qmljsindenter.h $$PWD/qmljsindenter.cpp \
$$PWD/qmljscodeformatter.cpp
HEADERS += \
$$PWD/qmljsindenter.h \
$$PWD/qmljscodeformatter.h
} }

View File

@@ -0,0 +1,910 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "qmljscodeformatter.h"
#include <QtCore/QDebug>
#include <QtCore/QMetaEnum>
#include <QtGui/QTextDocument>
#include <QtGui/QTextCursor>
#include <QtGui/QTextBlock>
using namespace QmlJS;
CodeFormatter::BlockData::BlockData()
: m_blockRevision(-1)
{
}
CodeFormatter::CodeFormatter()
: m_indentDepth(0)
, m_tabSize(4)
{
}
CodeFormatter::~CodeFormatter()
{
}
void CodeFormatter::setTabSize(int tabSize)
{
m_tabSize = tabSize;
}
void CodeFormatter::recalculateStateAfter(const QTextBlock &block)
{
restoreCurrentState(block.previous());
const int lexerState = tokenizeBlock(block);
m_tokenIndex = 0;
m_newStates.clear();
//qDebug() << "Starting to look at " << block.text() << block.blockNumber() + 1;
for (; m_tokenIndex < m_tokens.size(); ) {
m_currentToken = tokenAt(m_tokenIndex);
const int kind = extendedTokenKind(m_currentToken);
//dump();
//qDebug() << "Token" << m_currentLine.mid(m_currentToken.begin(), m_currentToken.length) << m_tokenIndex << "in line" << block.blockNumber() + 1;
if (kind == Comment
&& state().type != multiline_comment_cont
&& state().type != multiline_comment_start) {
m_tokenIndex += 1;
continue;
}
switch (m_currentState.top().type) {
case topmost_intro:
switch (kind) {
case Identifier: enter(objectdefinition_or_js); continue;
case Import: enter(top_qml); continue;
default: enter(top_js); continue;
} break;
case top_qml:
switch (kind) {
case Import: enter(import_start); break;
case Identifier: enter(binding_or_objectdefinition); break;
} break;
case top_js:
tryStatement();
break;
case objectdefinition_or_js:
switch (kind) {
case Dot: break;
case Identifier:
if (!m_currentLine.at(m_currentToken.begin()).isUpper()) {
turnInto(top_js);
continue;
}
break;
case LeftBrace: turnInto(binding_or_objectdefinition); continue;
default: turnInto(top_js); continue;
} break;
case import_start:
enter(import_maybe_dot_or_version_or_as);
break;
case import_maybe_dot_or_version_or_as:
switch (kind) {
case Dot: turnInto(import_dot); break;
case As: turnInto(import_as); break;
case Number: turnInto(import_maybe_as); break;
default: leave(); leave(); continue;
} break;
case import_maybe_as:
switch (kind) {
case As: turnInto(import_as); break;
default: leave(); leave(); continue;
} break;
case import_dot:
switch (kind) {
case Identifier: turnInto(import_maybe_dot_or_version_or_as); break;
default: leave(); leave(); continue;
} break;
case import_as:
switch (kind) {
case Identifier: leave(); leave(); break;
} break;
case binding_or_objectdefinition:
switch (kind) {
case Colon: enter(binding_assignment); break;
case LeftBrace: enter(objectdefinition_open); break;
} break;
case binding_assignment:
switch (kind) {
case Semicolon: leave(true); break;
case If: enter(if_statement); break;
case LeftBrace: enter(jsblock_open); break;
case On:
case As:
case List:
case Import:
case Signal:
case Property:
case Identifier: enter(expression_or_objectdefinition); break;
default: enter(expression); continue;
} break;
case objectdefinition_open:
switch (kind) {
case RightBrace: leave(true); break;
case Default: enter(default_property_start); break;
case Property: enter(property_start); break;
case Function: enter(function_start); break;
case Signal: enter(signal_start); break;
case On:
case As:
case List:
case Import:
case Identifier: enter(binding_or_objectdefinition); break;
} break;
case default_property_start:
if (kind != Property)
leave(true);
else
turnInto(property_start);
break;
case property_start:
switch (kind) {
case Colon: enter(binding_assignment); break; // oops, was a binding
case Var:
case Identifier: enter(property_type); break;
case List: enter(property_list_open); break;
default: leave(true); continue;
} break;
case property_type:
turnInto(property_maybe_initializer);
break;
case property_list_open:
if (m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length) == QLatin1String(">"))
turnInto(property_maybe_initializer);
break;
case property_maybe_initializer:
switch (kind) {
case Colon: enter(binding_assignment); break;
default: leave(true); continue;
} break;
case signal_start:
switch (kind) {
case Colon: enter(binding_assignment); break; // oops, was a binding
default: enter(signal_maybe_arglist); break;
} break;
case signal_maybe_arglist:
switch (kind) {
case LeftParenthesis: turnInto(signal_arglist_open); break;
default: leave(true); continue;
} break;
case signal_arglist_open:
switch (kind) {
case RightParenthesis: leave(true); break;
} break;
case function_start:
switch (kind) {
case LeftParenthesis: enter(function_arglist_open); break;
} break;
case function_arglist_open:
switch (kind) {
case RightParenthesis: turnInto(function_arglist_closed); break;
} break;
case function_arglist_closed:
switch (kind) {
case LeftBrace: turnInto(jsblock_open); break;
default: leave(true); continue; // error recovery
} break;
case expression_or_objectdefinition:
switch (kind) {
case LeftBrace: turnInto(objectdefinition_open); break;
default: enter(expression); continue; // really? first token already gone!
} break;
case expression:
if (tryInsideExpression())
break;
switch (kind) {
case Comma:
case Delimiter: enter(expression_continuation); break;
case RightBracket:
case RightParenthesis: leave(); continue;
case RightBrace: leave(true); continue;
case Semicolon: leave(true); break;
} break;
case expression_continuation:
leave();
continue;
case expression_maybe_continuation:
switch (kind) {
case Question:
case Delimiter:
case LeftBracket:
case LeftParenthesis: leave(); continue;
default: leave(true); continue;
} break;
case paren_open:
if (tryInsideExpression())
break;
switch (kind) {
case RightParenthesis: leave(); break;
} break;
case bracket_open:
if (tryInsideExpression())
break;
switch (kind) {
case Comma: enter(bracket_element_start); break;
case RightBracket: leave(); break;
} break;
case bracket_element_start:
switch (kind) {
case Identifier: turnInto(bracket_element_maybe_objectdefinition); break;
default: leave(); continue;
} break;
case bracket_element_maybe_objectdefinition:
switch (kind) {
case LeftBrace: turnInto(objectdefinition_open); break;
default: leave(); continue;
} break;
case ternary_op:
if (tryInsideExpression())
break;
switch (kind) {
case RightParenthesis:
case RightBracket:
case RightBrace:
case Comma:
case Semicolon: leave(); continue;
case Colon: enter(expression); break; // entering expression makes maybe_continuation work
} break;
case jsblock_open:
case substatement_open:
if (tryStatement())
break;
switch (kind) {
case RightBrace: leave(true); break;
} break;
case substatement:
// prefer substatement_open over block_open
if (kind != LeftBrace) {
if (tryStatement())
break;
}
switch (kind) {
case LeftBrace: turnInto(substatement_open); break;
} break;
case if_statement:
switch (kind) {
case LeftParenthesis: enter(condition_open); break;
default: leave(true); break; // error recovery
} break;
case maybe_else:
if (kind == 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 condition_open:
switch (kind) {
case RightParenthesis: turnInto(substatement); break;
case LeftParenthesis: enter(condition_paren_open); break;
} break;
// paren nesting
case condition_paren_open:
switch (kind) {
case RightParenthesis: leave(); break;
case LeftParenthesis: enter(condition_paren_open); break;
} break;
case switch_statement:
case statement_with_condition:
switch (kind) {
case LeftParenthesis: enter(statement_with_condition_paren_open); break;
default: leave(true);
} break;
case statement_with_condition_paren_open:
if (tryInsideExpression())
break;
switch (kind) {
case RightParenthesis: turnInto(substatement); break;
} break;
case statement_with_block:
switch (kind) {
case LeftBrace: enter(jsblock_open); break;
default: leave(true); break;
} break;
case do_statement:
switch (kind) {
case While: break;
case LeftParenthesis: enter(do_statement_while_paren_open); break;
default: leave(true); break;
} break;
case do_statement_while_paren_open:
if (tryInsideExpression())
break;
switch (kind) {
case RightParenthesis: leave(); leave(true); break;
} break;
break;
case case_start:
switch (kind) {
case Colon: turnInto(case_cont); break;
} break;
case case_cont:
if (kind != Case && kind != Default && tryStatement())
break;
switch (kind) {
case RightBrace: leave(); continue;
case Default:
case Case: leave(); continue;
} break;
case multiline_comment_start:
case multiline_comment_cont:
if (kind != Comment) {
leave();
continue;
} else if (m_tokenIndex == m_tokens.size() - 1
&& lexerState == Scanner::Normal) {
leave();
} else if (m_tokenIndex == 0) {
// to allow enter/leave to update the indentDepth
turnInto(multiline_comment_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 == expression
|| topState == expression_or_objectdefinition) {
enter(expression_maybe_continuation);
}
if (topState != multiline_comment_start
&& topState != multiline_comment_cont
&& lexerState == Scanner::MultiLineComment) {
enter(multiline_comment_start);
}
saveCurrentState(block);
}
int CodeFormatter::indentFor(const QTextBlock &block)
{
// qDebug() << "indenting for" << block.blockNumber() + 1;
restoreCurrentState(block.previous());
correctIndentation(block);
return m_indentDepth;
}
void CodeFormatter::updateStateUntil(const QTextBlock &endBlock)
{
QStack<State> previousState = initialState();
QTextBlock it = endBlock.document()->firstBlock();
// find the first block that needs recalculation
for (; it.isValid() && it != endBlock; it = it.next()) {
BlockData blockData;
if (!loadBlockData(it, &blockData))
break;
if (blockData.m_blockRevision != it.revision())
break;
if (previousState != blockData.m_beginState)
break;
if (loadLexerState(it) == -1)
break;
previousState = blockData.m_endState;
}
if (it == endBlock)
return;
// update everthing until endBlock
for (; it.isValid() && it != endBlock; it = it.next()) {
recalculateStateAfter(it);
}
// invalidate everything below by marking the state in endBlock as invalid
if (it.isValid()) {
BlockData invalidBlockData;
saveBlockData(&it, invalidBlockData);
}
}
void CodeFormatter::updateLineStateChange(const QTextBlock &block)
{
if (!block.isValid())
return;
BlockData blockData;
if (loadBlockData(block, &blockData) && blockData.m_blockRevision == block.revision())
return;
recalculateStateAfter(block);
// invalidate everything below by marking the next block's state as invalid
QTextBlock next = block.next();
if (!next.isValid())
return;
saveBlockData(&next, BlockData());
}
CodeFormatter::State CodeFormatter::state(int belowTop) const
{
if (belowTop < m_currentState.size())
return m_currentState.at(m_currentState.size() - 1 - belowTop);
else
return State();
}
const QVector<CodeFormatter::State> &CodeFormatter::newStatesThisLine() const
{
return m_newStates;
}
int CodeFormatter::tokenIndex() const
{
return m_tokenIndex;
}
int CodeFormatter::tokenCount() const
{
return m_tokens.size();
}
const Token &CodeFormatter::currentToken() const
{
return m_currentToken;
}
void CodeFormatter::invalidateCache(QTextDocument *document)
{
if (!document)
return;
BlockData invalidBlockData;
QTextBlock it = document->firstBlock();
for (; it.isValid(); it = it.next()) {
saveBlockData(&it, invalidBlockData);
}
}
void CodeFormatter::enter(int newState)
{
int savedIndentDepth = m_indentDepth;
onEnter(newState, &m_indentDepth, &savedIndentDepth);
State s(newState, savedIndentDepth);
m_currentState.push(s);
m_newStates.push(s);
if (newState == bracket_open)
enter(bracket_element_start);
}
void CodeFormatter::leave(bool statementDone)
{
Q_ASSERT(m_currentState.size() > 1);
if (m_currentState.top().type == topmost_intro)
return;
if (m_newStates.size() > 0)
m_newStates.pop();
// restore indent depth
State poppedState = m_currentState.pop();
m_indentDepth = poppedState.savedIndentDepth;
int topState = m_currentState.top().type;
// if statement is done, may need to leave recursively
if (statementDone) {
if (!isExpressionEndState(topState))
leave(true);
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::tryInsideExpression(bool alsoExpression)
{
int newState = -1;
const int kind = extendedTokenKind(m_currentToken);
switch (kind) {
case LeftParenthesis: newState = paren_open; break;
case LeftBracket: newState = bracket_open; break;
case Function: newState = function_start; break;
case Question: newState = ternary_op; break;
}
if (newState != -1) {
if (alsoExpression)
enter(expression);
enter(newState);
return true;
}
return false;
}
bool CodeFormatter::tryStatement()
{
const int kind = extendedTokenKind(m_currentToken);
switch (kind) {
case Semicolon:
enter(empty_statement);
leave(true);
return true;
case Return:
enter(return_statement);
enter(expression);
return true;
case While:
case For:
case Catch:
enter(statement_with_condition);
return true;
case Switch:
enter(switch_statement);
return true;
case If:
enter(if_statement);
return true;
case Do:
enter(do_statement);
enter(substatement);
return true;
case Case:
case Default:
enter(case_start);
return true;
case Try:
case Finally:
enter(statement_with_block);
return true;
case LeftBrace:
enter(jsblock_open);
return true;
case Identifier:
case Delimiter:
case PlusPlus:
case MinusMinus:
case Import:
case Signal:
case On:
case As:
case List:
case Property:
case Function:
enter(expression);
// look at the token again
m_tokenIndex -= 1;
return true;
}
return false;
}
bool CodeFormatter::isBracelessState(int type) const
{
return
type == if_statement ||
type == else_clause ||
type == substatement ||
type == binding_assignment ||
type == binding_or_objectdefinition;
}
bool CodeFormatter::isExpressionEndState(int type) const
{
return
type == topmost_intro ||
type == top_js ||
type == objectdefinition_open ||
type == if_statement ||
type == else_clause ||
type == do_statement ||
type == jsblock_open ||
type == substatement_open ||
type == bracket_open ||
type == paren_open ||
type == case_cont;
}
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);
}
int CodeFormatter::column(int index) const
{
int col = 0;
if (index > m_currentLine.length())
index = m_currentLine.length();
const QChar tab = QLatin1Char('\t');
for (int i = 0; i < index; i++) {
if (m_currentLine[i] == tab) {
col = ((col / m_tabSize) + 1) * m_tabSize;
} else {
col++;
}
}
return col;
}
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::saveCurrentState(const QTextBlock &block)
{
if (!block.isValid())
return;
BlockData blockData;
blockData.m_blockRevision = block.revision();
blockData.m_beginState = m_beginState;
blockData.m_endState = m_currentState;
blockData.m_indentDepth = m_indentDepth;
QTextBlock saveableBlock(block);
saveBlockData(&saveableBlock, blockData);
}
void CodeFormatter::restoreCurrentState(const QTextBlock &block)
{
if (block.isValid()) {
BlockData blockData;
if (loadBlockData(block, &blockData)) {
m_indentDepth = blockData.m_indentDepth;
m_currentState = blockData.m_endState;
m_beginState = m_currentState;
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)
{
int startState = loadLexerState(block.previous());
if (block.blockNumber() == 0)
startState = 0;
Q_ASSERT(startState != -1);
Scanner tokenize;
tokenize.setScanComments(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);
const int lexerState = tokenize.state();
QTextBlock saveableBlock(block);
saveLexerState(&saveableBlock, lexerState);
return lexerState;
}
CodeFormatter::TokenKind CodeFormatter::extendedTokenKind(const QmlJS::Token &token) const
{
const int kind = token.kind;
QStringRef text = m_currentLine.midRef(token.begin(), token.length);
if (kind == Identifier) {
if (text == "as")
return As;
if (text == "import")
return Import;
if (text == "signal")
return Signal;
if (text == "property")
return Property;
if (text == "on")
return On;
if (text == "list")
return On;
} else if (kind == Keyword) {
const QChar char1 = text.at(0);
const QChar char2 = text.at(1);
const QChar char3 = (text.size() > 2 ? text.at(2) : QChar());
switch (char1.toLatin1()) {
case 'v':
return Var;
case 'i':
if (char2 == 'f')
return If;
else if (char3 == 's')
return Instanceof;
else
return In;
case 'f':
if (char2 == 'o')
return For;
else if (char2 == 'u')
return Function;
else
return Finally;
case 'e':
return Else;
case 'n':
return New;
case 'r':
return Return;
case 's':
return Switch;
case 'w':
if (char2 == 'h')
return While;
return With;
case 'c':
if (char3 == 's')
return Case;
if (char3 == 't')
return Catch;
return Continue;
case 'd':
if (char3 == 'l')
return Delete;
if (char3 == 'f')
return Default;
if (char3 == 'b')
return Debugger;
return Do;
case 't':
if (char3 == 'i')
return This;
if (char3 == 'y')
return Try;
if (char3 == 'r')
return Throw;
return Typeof;
case 'b':
return Break;
}
} else if (kind == Delimiter) {
if (text == "?")
return Question;
else if (text == "++")
return PlusPlus;
else if (text == "--")
return MinusMinus;
}
return static_cast<TokenKind>(kind);
}
void CodeFormatter::dump() const
{
QMetaEnum metaEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType"));
qDebug() << "Current token index" << m_tokenIndex;
qDebug() << "Current state:";
foreach (State s, m_currentState) {
qDebug() << metaEnum.valueToKey(s.type) << s.savedIndentDepth;
}
qDebug() << "Current indent depth:" << m_indentDepth;
}

View File

@@ -0,0 +1,309 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef QMLJSCODEFORMATTER_H
#define QMLJSCODEFORMATTER_H
#include "qmljs_global.h"
#include "qmljsscanner.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 QmlJS {
class QMLJS_EXPORT CodeFormatter
{
Q_GADGET
public:
CodeFormatter();
virtual ~CodeFormatter();
// updates all states up until block if necessary
// it is safe to call indentFor on block afterwards
void updateStateUntil(const QTextBlock &block);
// calculates the state change introduced by changing a single line
void updateLineStateChange(const QTextBlock &block);
int indentFor(const QTextBlock &block);
void setTabSize(int tabSize);
void invalidateCache(QTextDocument *document);
protected:
virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const = 0;
virtual void adjustIndent(const QList<Token> &tokens, int lexerState, int *indentDepth) const = 0;
class State;
class BlockData
{
public:
BlockData();
QStack<State> m_beginState;
QStack<State> m_endState;
int m_indentDepth;
int m_blockRevision;
};
virtual void saveBlockData(QTextBlock *block, const BlockData &data) const = 0;
virtual bool loadBlockData(const QTextBlock &block, BlockData *data) const = 0;
virtual void saveLexerState(QTextBlock *block, int state) const = 0;
virtual int loadLexerState(const QTextBlock &block) const = 0;
public: // must be public to make Q_GADGET introspection work
enum StateType {
invalid = 0,
topmost_intro, // The first line in a "topmost" definition.
top_qml, // root state for qml
top_js, // root for js
objectdefinition_or_js, // file starts with identifier
multiline_comment_start,
multiline_comment_cont,
import_start, // after 'import'
import_maybe_dot_or_version_or_as, // after string or identifier
import_dot, // after .
import_maybe_as, // after version
import_as,
property_start, // after 'property'
default_property_start, // after 'default'
property_type, // after first identifier
property_list_open, // after 'list' as a type
property_maybe_initializer, // after
signal_start, // after 'signal'
signal_maybe_arglist, // after identifier
signal_arglist_open, // after '('
function_start, // after 'function'
function_arglist_open, // after '(' starting function argument list
function_arglist_closed, // after ')' in argument list, expecting '{'
binding_or_objectdefinition, // after an identifier
binding_assignment, // after :
objectdefinition_open, // after {
expression,
expression_continuation, // at the end of the line, when the next line definitely is a continuation
expression_maybe_continuation, // at the end of the line, when the next line may be an expression
expression_or_objectdefinition, // after a binding starting with an identifier ("x: foo")
paren_open, // opening ( in expression
bracket_open, // opening [ in expression
bracket_element_start, // after starting bracket_open or after ',' in bracket_open
bracket_element_maybe_objectdefinition, // after an identifier in bracket_element_start
ternary_op, // The ? : operator
jsblock_open,
empty_statement, // for a ';', will never linger
if_statement, // After 'if'
maybe_else, // after the first substatement in an if
else_clause, // The else line of an if-else construct.
condition_open, // Start of a condition in 'if', 'while', entered after opening paren
condition_paren_open, // After an lparen in a condition
substatement, // The first line after a conditional or loop construct.
substatement_open, // The brace that opens a substatement block.
return_statement, // After 'return'
statement_with_condition, // After the 'for', 'while', 'catch', ... token
statement_with_condition_paren_open, // While inside the (...)
statement_with_block, // try, finally
do_statement, // after 'do'
do_statement_while_paren_open, // after '(' in while clause
switch_statement, // After 'switch' token
case_start, // after a 'case' or 'default' token
case_cont, // after the colon in a case/default
};
Q_ENUMS(StateType)
protected:
// extends Token::Kind from qmljsscanner.h
// the entries until EndOfExistingTokenKinds must match
enum TokenKind {
EndOfFile,
Keyword,
Identifier,
String,
Comment,
Number,
LeftParenthesis,
RightParenthesis,
LeftBrace,
RightBrace,
LeftBracket,
RightBracket,
Semicolon,
Colon,
Comma,
Dot,
Delimiter,
EndOfExistingTokenKinds,
Break,
Case,
Catch,
Continue,
Debugger,
Default,
Delete,
Do,
Else,
Finally,
For,
Function,
If,
In,
Instanceof,
New,
Return,
Switch,
This,
Throw,
Try,
Typeof,
Var,
Void,
While,
With,
Import,
Signal,
On,
As,
List,
Property,
Question,
PlusPlus,
MinusMinus,
};
TokenKind extendedTokenKind(const QmlJS::Token &token) const;
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;
const QVector<State> &newStatesThisLine() const;
int tokenIndex() const;
int tokenCount() const;
const Token &currentToken() const;
const Token &tokenAt(int idx) const;
int column(int position) const;
bool isBracelessState(int type) const;
bool isExpressionEndState(int type) const;
void dump() const;
private:
void recalculateStateAfter(const QTextBlock &block);
void saveCurrentState(const QTextBlock &block);
void restoreCurrentState(const QTextBlock &block);
QStringRef currentTokenText() const;
int tokenizeBlock(const QTextBlock &block);
void turnInto(int newState);
bool tryInsideExpression(bool alsoExpression = false);
bool tryStatement();
void enter(int newState);
void leave(bool statementDone = false);
void correctIndentation(const QTextBlock &block);
private:
static QStack<State> initialState();
QStack<State> m_beginState;
QStack<State> m_currentState;
QStack<State> m_newStates;
QList<Token> m_tokens;
QString m_currentLine;
Token m_currentToken;
int m_tokenIndex;
// should store indent level and padding instead
int m_indentDepth;
int m_tabSize;
};
} // namespace QmlJS
#endif // QMLJSCODEFORMATTER_H

View File

@@ -33,8 +33,8 @@
#include "qmljseditorplugin.h" #include "qmljseditorplugin.h"
#include "qmljsmodelmanager.h" #include "qmljsmodelmanager.h"
#include "qmloutlinemodel.h" #include "qmloutlinemodel.h"
#include "qmljseditorcodeformatter.h"
#include <qmljs/qmljsindenter.h>
#include <qmljs/qmljsbind.h> #include <qmljs/qmljsbind.h>
#include <qmljs/qmljscheck.h> #include <qmljs/qmljscheck.h>
#include <qmljs/qmljsdocument.h> #include <qmljs/qmljsdocument.h>
@@ -1276,15 +1276,25 @@ bool QmlJSTextEditor::isClosingBrace(const QList<Token> &tokens) const
return false; return false;
} }
static QmlJSEditor::QtStyleCodeFormatter setupCodeFormatter(const TextEditor::TabSettings &ts)
{
QmlJSEditor::QtStyleCodeFormatter codeFormatter;
codeFormatter.setIndentSize(ts.m_indentSize);
codeFormatter.setTabSize(ts.m_tabSize);
return codeFormatter;
}
void QmlJSTextEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar) void QmlJSTextEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar)
{ {
TextEditor::TabSettings ts = tabSettings(); Q_UNUSED(doc)
QmlJSIndenter indenter; Q_UNUSED(typedChar)
indenter.setTabSize(ts.m_tabSize);
indenter.setIndentSize(ts.m_indentSize);
const int indent = indenter.indentForBottomLine(doc->begin(), block.next(), typedChar); const TextEditor::TabSettings &ts = tabSettings();
ts.indentLine(block, indent); QmlJSEditor::QtStyleCodeFormatter codeFormatter = setupCodeFormatter(ts);
codeFormatter.updateStateUntil(block);
const int depth = codeFormatter.indentFor(block);
ts.indentLine(block, depth);
} }
TextEditor::BaseTextEditorEditable *QmlJSTextEditor::createEditableInterface() TextEditor::BaseTextEditorEditable *QmlJSTextEditor::createEditableInterface()

View File

@@ -26,7 +26,8 @@ HEADERS += \
qmljscomponentfromobjectdef.h \ qmljscomponentfromobjectdef.h \
qmljsoutline.h \ qmljsoutline.h \
qmloutlinemodel.h \ qmloutlinemodel.h \
qmltaskmanager.h qmltaskmanager.h \
qmljseditorcodeformatter.h
SOURCES += \ SOURCES += \
qmljscodecompletion.cpp \ qmljscodecompletion.cpp \
@@ -46,7 +47,8 @@ SOURCES += \
qmljsoutline.cpp \ qmljsoutline.cpp \
qmloutlinemodel.cpp \ qmloutlinemodel.cpp \
qmltaskmanager.cpp \ qmltaskmanager.cpp \
qmljsquickfixes.cpp qmljsquickfixes.cpp \
qmljseditorcodeformatter.cpp
RESOURCES += qmljseditor.qrc RESOURCES += qmljseditor.qrc
OTHER_FILES += QmlJSEditor.pluginspec QmlJSEditor.mimetypes.xml OTHER_FILES += QmlJSEditor.pluginspec QmlJSEditor.mimetypes.xml

View File

@@ -0,0 +1,358 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "qmljseditorcodeformatter.h"
#include <QtCore/QDebug>
using namespace QmlJS;
using namespace QmlJSEditor;
using namespace TextEditor;
QtStyleCodeFormatter::QtStyleCodeFormatter()
: m_indentSize(4)
{
}
void QtStyleCodeFormatter::setIndentSize(int size)
{
m_indentSize = size;
}
void QtStyleCodeFormatter::saveBlockData(QTextBlock *block, const BlockData &data) const
{
TextBlockUserData *userData = BaseTextDocumentLayout::userData(*block);
QmlJSCodeFormatterData *cppData = static_cast<QmlJSCodeFormatterData *>(userData->codeFormatterData());
if (!cppData) {
cppData = new QmlJSCodeFormatterData;
userData->setCodeFormatterData(cppData);
}
cppData->m_data = data;
}
bool QtStyleCodeFormatter::loadBlockData(const QTextBlock &block, BlockData *data) const
{
TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block);
if (!userData)
return false;
QmlJSCodeFormatterData *cppData = static_cast<QmlJSCodeFormatterData *>(userData->codeFormatterData());
if (!cppData)
return false;
*data = cppData->m_data;
return true;
}
void QtStyleCodeFormatter::saveLexerState(QTextBlock *block, int state) const
{
BaseTextDocumentLayout::setLexerState(*block, state);
}
int QtStyleCodeFormatter::loadLexerState(const QTextBlock &block) const
{
return BaseTextDocumentLayout::lexerState(block);
}
void QtStyleCodeFormatter::onEnter(int newState, int *indentDepth, int *savedIndentDepth) const
{
const State &parentState = state();
const Token &tk = currentToken();
const int tokenPosition = column(tk.begin());
const bool firstToken = (tokenIndex() == 0);
const bool lastToken = (tokenIndex() == tokenCount() - 1);
switch (newState) {
case objectdefinition_open: {
// special case for things like "gradient: Gradient {"
if (parentState.type == binding_assignment)
*savedIndentDepth = state(1).savedIndentDepth;
bool followedByData = (!lastToken && !tokenAt(tokenIndex() + 1).kind == Token::Comment);
if (firstToken || followedByData)
*savedIndentDepth = tokenPosition;
*indentDepth = *savedIndentDepth;
if (followedByData) {
*indentDepth = column(tokenAt(tokenIndex() + 1).begin());
} else {
*indentDepth += m_indentSize;
}
break;
}
case binding_or_objectdefinition:
if (firstToken)
*indentDepth = *savedIndentDepth = tokenPosition;
break;
case binding_assignment:
if (lastToken)
*indentDepth = *savedIndentDepth + 4;
else
*indentDepth = column(tokenAt(tokenIndex() + 1).begin());
break;
case expression_or_objectdefinition:
*indentDepth = tokenPosition;
break;
case expression:
// expression_or_objectdefinition has already consumed the first token
// ternary already adjusts indents nicely
if (parentState.type != expression_or_objectdefinition
&& parentState.type != binding_assignment
&& parentState.type != ternary_op) {
*indentDepth += 2 * m_indentSize;
}
if (!firstToken && parentState.type != expression_or_objectdefinition) {
*indentDepth = tokenPosition;
}
break;
case expression_maybe_continuation:
// set indent depth to indent we'd get if the expression ended here
for (int i = 1; state(i).type != topmost_intro; ++i) {
const int type = state(i).type;
if (isExpressionEndState(type) && !isBracelessState(type)) {
*indentDepth = state(i - 1).savedIndentDepth;
break;
}
}
break;
case bracket_open:
if (parentState.type == expression && state(1).type == binding_assignment) {
*savedIndentDepth = state(2).savedIndentDepth;
*indentDepth = *savedIndentDepth + m_indentSize;
} else if (!lastToken) {
*indentDepth = tokenPosition + 1;
} else {
*indentDepth = *savedIndentDepth + m_indentSize;
}
break;
case function_start:
if (parentState.type == expression) {
// undo the continuation indent of the expression
*indentDepth = parentState.savedIndentDepth;
*savedIndentDepth = *indentDepth;
}
break;
case do_statement_while_paren_open:
case statement_with_condition_paren_open:
case signal_arglist_open:
case function_arglist_open:
case paren_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 jsblock_open:
// closing brace should be aligned to case
if (parentState.type == case_cont) {
*savedIndentDepth = parentState.savedIndentDepth;
break;
}
// fallthrough
case substatement_open:
// special case for foo: {
if (parentState.type == binding_assignment && state(1).type == binding_or_objectdefinition)
*savedIndentDepth = state(1).savedIndentDepth;
*indentDepth = *savedIndentDepth + m_indentSize;
break;
case statement_with_condition:
case statement_with_block:
case if_statement:
case do_statement:
case switch_statement:
if (firstToken || parentState.type == binding_assignment)
*savedIndentDepth = tokenPosition;
// ### continuation
*indentDepth = *savedIndentDepth; // + 2*m_indentSize;
break;
case maybe_else: {
// set indent to outermost braceless savedIndent
int outermostBraceless = 0;
while (isBracelessState(state(outermostBraceless + 1).type))
++outermostBraceless;
*indentDepth = state(outermostBraceless).savedIndentDepth;
// this is where the else should go, if one appears - aligned to if_statement
*savedIndentDepth = state().savedIndentDepth;
break;
}
case condition_open:
// 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 case_start:
*savedIndentDepth = tokenPosition;
break;
case case_cont:
*indentDepth += m_indentSize;
break;
case multiline_comment_start:
*indentDepth = tokenPosition + 2;
break;
case multiline_comment_cont:
*indentDepth = tokenPosition;
break;
}
}
void QtStyleCodeFormatter::adjustIndent(const QList<Token> &tokens, int lexerState, int *indentDepth) const
{
Q_UNUSED(lexerState)
State topState = state();
State previousState = state(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 = column(tokens.at(0).begin());
return;
}
}
const int kind = extendedTokenKind(tokenAt(0));
switch (kind) {
case LeftBrace:
if (topState.type == substatement
|| topState.type == binding_assignment
|| topState.type == case_cont) {
*indentDepth = topState.savedIndentDepth;
}
break;
case RightBrace: {
if (topState.type == jsblock_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 == objectdefinition_open
|| type == jsblock_open
|| type == substatement_open) {
*indentDepth = state(i).savedIndentDepth;
break;
}
}
break;
}
case RightBracket:
for (int i = 0; state(i).type != topmost_intro; ++i) {
const int type = state(i).type;
if (type == bracket_open) {
*indentDepth = state(i).savedIndentDepth;
break;
}
}
break;
case LeftBracket:
case LeftParenthesis:
case Delimiter:
if (topState.type == expression_maybe_continuation)
*indentDepth = topState.savedIndentDepth;
break;
case Else:
if (topState.type == maybe_else) {
*indentDepth = topState.savedIndentDepth;
} else if (topState.type == expression_maybe_continuation) {
bool hasElse = false;
for (int i = 1; state(i).type != topmost_intro; ++i) {
const int type = state(i).type;
if (type == else_clause)
hasElse = true;
if (type == if_statement) {
if (hasElse) {
hasElse = false;
} else {
*indentDepth = state(i).savedIndentDepth;
break;
}
}
}
}
break;
case Colon:
if (topState.type == ternary_op) {
*indentDepth -= 2;
}
break;
case Question:
if (topState.type == expression_maybe_continuation)
*indentDepth = topState.savedIndentDepth;
break;
case Default:
case 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;
}
}

View File

@@ -0,0 +1,69 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef QMLJSEDITORCODEFORMATTER_H
#define QMLJSEDITORCODEFORMATTER_H
#include "qmljseditor_global.h"
#include <texteditor/basetextdocumentlayout.h>
#include <qmljs/qmljscodeformatter.h>
namespace QmlJSEditor {
class QMLJSEDITOR_EXPORT QtStyleCodeFormatter : public QmlJS::CodeFormatter
{
public:
QtStyleCodeFormatter();
void setIndentSize(int size);
protected:
virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const;
virtual void adjustIndent(const QList<QmlJS::Token> &tokens, int lexerState, int *indentDepth) const;
virtual void saveBlockData(QTextBlock *block, const BlockData &data) const;
virtual bool loadBlockData(const QTextBlock &block, BlockData *data) const;
virtual void saveLexerState(QTextBlock *block, int state) const;
virtual int loadLexerState(const QTextBlock &block) const;
private:
int m_indentSize;
class QmlJSCodeFormatterData: public TextEditor::CodeFormatterData
{
public:
QmlJS::CodeFormatter::BlockData m_data;
};
};
} // namespace QmlJSEditor
#endif // QMLJSEDITORCODEFORMATTER_H

View File

@@ -0,0 +1,24 @@
TEMPLATE = app
CONFIG += qt warn_on console depend_includepath
QT += testlib network
SRCDIR = ../../../../../src
#DEFINES += QML_BUILD_STATIC_LIB
#include($$SRCDIR/../qtcreator.pri)
include($$SRCDIR/libs/qmljs/qmljs-lib.pri)
include($$SRCDIR/libs/utils/utils-lib.pri)
#LIBS += -L$$IDE_LIBRARY_PATH
SOURCES += \
tst_codeformatter.cpp \
$$SRCDIR/plugins/qmljseditor/qmljseditorcodeformatter.cpp \
$$SRCDIR/plugins/texteditor/basetextdocumentlayout.cpp
HEADERS += \
$$SRCDIR/plugins/qmljseditor/qmljseditorcodeformatter.h \
$$SRCDIR/plugins/texteditor/basetextdocumentlayout.h \
INCLUDEPATH += $$SRCDIR/plugins $$SRCDIR/libs
TARGET=tst_$$TARGET

View File

@@ -0,0 +1,888 @@
#include <QtTest>
#include <QObject>
#include <QList>
#include <QTextDocument>
#include <QTextBlock>
#include <qmljseditor/qmljseditorcodeformatter.h>
using namespace QmlJSEditor;
class tst_CodeFormatter: public QObject
{
Q_OBJECT
private Q_SLOTS:
void objectDefinitions1();
void objectDefinitions2();
void expressionEndSimple();
void expressionEnd();
void expressionEndBracket();
void expressionEndParen();
void objectBinding();
void arrayBinding();
void functionDeclaration();
void functionExpression();
void propertyDeclarations();
void signalDeclarations();
void ifBinding1();
void ifBinding2();
void ifStatementWithoutBraces1();
void ifStatementWithoutBraces2();
void ifStatementWithBraces1();
void ifStatementWithBraces2();
void ifStatementMixed();
void ifStatementAndComments();
void ifStatementLongCondition();
void moreIfThenElse();
void strayElse();
void oneLineIf();
void forStatement();
void whileStatement();
void tryStatement();
void doWhile();
void cStyleComments();
void cppStyleComments();
void qmlKeywords();
void ternary();
void switch1();
// void gnuStyle();
// void whitesmithsStyle();
void expressionContinuation();
};
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, int style = 0)
{
Q_UNUSED(style)
QString text = concatLines(data);
QTextDocument document(text);
QtStyleCodeFormatter formatter;
int i = 0;
foreach (const Line &l, data) {
QTextBlock b = document.findBlockByLineNumber(i);
if (l.expectedIndent != -1) {
int actualIndent = formatter.indentFor(b);
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());
}
}
formatter.updateLineStateChange(b);
++i;
}
}
void tst_CodeFormatter::objectDefinitions1()
{
QList<Line> data;
data << Line("import Qt 4.7")
<< Line("")
<< Line("Rectangle {")
<< Line(" foo: bar;")
<< Line(" Item {")
<< Line(" x: 42;")
<< Line(" y: x;")
<< Line(" }")
<< Line(" Component.onCompleted: foo;")
<< Line(" ")
<< Line(" Foo.Bar {")
<< Line(" width: 12 + 54;")
<< Line(" anchors.fill: parent;")
<< Line(" }")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::objectDefinitions2()
{
QList<Line> data;
data << Line("import Qt 4.7")
<< Line("")
<< Line("Rectangle {")
<< Line(" foo: bar;")
<< Line(" Image { source: \"a+b+c\"; x: 42; y: 12 }")
<< Line(" Component.onCompleted: foo;")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::expressionEndSimple()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar +")
<< Line(" foo(4, 5) +")
<< Line(" 7")
<< Line(" x: 42")
<< Line(" y: 43")
<< Line(" width: 10")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::expressionEnd()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar +")
<< Line(" foo(4")
<< Line(" + 5)")
<< Line(" + 7")
<< Line(" x: 42")
<< Line(" + 43")
<< Line(" + 10")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::expressionEndParen()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" (foo(4")
<< Line(" + 5)")
<< Line(" + 7,")
<< Line(" abc)")
<< Line(" x: a + b(fpp, ba + 12) + foo(")
<< Line(" bar,")
<< Line(" 10)")
<< Line(" + 10")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::expressionEndBracket()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" [foo[4")
<< Line(" + 5]")
<< Line(" + 7,")
<< Line(" abc]")
<< Line(" x: a + b[fpp, ba + 12] + foo[")
<< Line(" bar,")
<< Line(" 10]")
<< Line(" + 10")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::objectBinding()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" x: 3")
<< Line(" foo: Gradient {")
<< Line(" x: 12")
<< Line(" y: x")
<< Line(" }")
<< Line(" Item {")
<< Line(" states: State {}")
<< Line(" }")
<< Line(" x: 1")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::arrayBinding()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" x: 3")
<< Line(" foo: [")
<< Line(" State {")
<< Line(" y: x")
<< Line(" },")
<< Line(" State")
<< Line(" {")
<< Line(" }")
<< Line(" ]")
<< Line(" foo: [")
<< Line(" 1 +")
<< Line(" 2")
<< Line(" + 345 * foo(")
<< Line(" bar, car,")
<< Line(" dar),")
<< Line(" x, y,")
<< Line(" z,")
<< Line(" ]")
<< Line(" x: 1")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::moreIfThenElse()
{
QList<Line> data;
data << Line("Image {")
<< Line(" source: {")
<< Line(" if(type == 1) {")
<< Line(" \"pics/blueStone.png\";")
<< Line(" } else if (type == 2) {")
<< Line(" \"pics/head.png\";")
<< Line(" } else {")
<< Line(" \"pics/redStone.png\";")
<< Line(" }")
<< Line(" }")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::functionDeclaration()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" function foo(a, b, c) {")
<< Line(" if (a)")
<< Line(" b;")
<< Line(" }")
<< Line(" property alias boo :")
<< Line(" foo")
<< Line(" Item {")
<< Line(" property variant g : Gradient {")
<< Line(" v: 12")
<< Line(" }")
<< Line(" function bar(")
<< Line(" a, b,")
<< Line(" c)")
<< Line(" {")
<< Line(" var b")
<< Line(" }")
<< Line(" function bar(a,")
<< Line(" a, b,")
<< Line(" c)")
<< Line(" {")
<< Line(" var b")
<< Line(" }")
<< Line(" }")
<< Line(" x: 1")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::functionExpression()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line("onFoo: {", 4)
<< Line(" function foo(a, b, c) {")
<< Line(" if (a)")
<< Line(" b;")
<< Line(" }")
<< Line(" return function(a, b) { return a + b; }")
<< Line(" return function foo(a, b) {")
<< Line(" return a")
<< Line(" }")
<< Line("}")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::propertyDeclarations()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" property int foo : 2 +")
<< Line(" x")
<< Line(" property list<Foo> bar")
<< Line(" property alias boo :")
<< Line(" foo")
<< Line(" Item {")
<< Line(" property variant g : Gradient {")
<< Line(" v: 12")
<< Line(" }")
<< Line(" default property Item g")
<< Line(" default property Item g : parent.foo")
<< Line(" }")
<< Line(" x: 1")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::signalDeclarations()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" foo: bar")
<< Line(" signal foo")
<< Line(" x: bar")
<< Line(" signal bar(a, int b)")
<< Line(" signal bar2()")
<< Line(" Item {")
<< Line(" signal property")
<< Line(" signal import(a, b);")
<< Line(" }")
<< Line(" x: 1")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifBinding1()
{
QList<Line> data;
data << Line("A.Rectangle {")
<< Line(" foo: bar")
<< Line(" x: if (a) b")
<< Line(" x: if (a)")
<< Line(" b")
<< Line(" x: if (a) b;")
<< Line(" x: if (a)")
<< Line(" b;")
<< Line(" x: if (a) b; else c")
<< Line(" x: if (a) b")
<< Line(" else c")
<< Line(" x: if (a) b;")
<< Line(" else c")
<< Line(" x: if (a) b;")
<< Line(" else")
<< Line(" c")
<< Line(" x: if (a)")
<< Line(" b")
<< Line(" else")
<< Line(" c")
<< Line(" x: if (a) b; else c;")
<< Line(" x: 1")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifBinding2()
{
QList<Line> data;
data << Line("A.Rectangle {")
<< Line(" foo: bar")
<< Line(" x: if (a) b +")
<< Line(" 5 +")
<< Line(" 5 * foo(")
<< Line(" 1, 2)")
<< Line(" else a =")
<< Line(" foo(15,")
<< Line(" bar(")
<< Line(" 1),")
<< Line(" bar)")
<< Line(" x: if (a) b")
<< Line(" + 5")
<< Line(" + 5")
<< Line(" x: if (a)")
<< Line(" b")
<< Line(" + 5")
<< Line(" + 5")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithoutBraces1()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" x: if (a)")
<< Line(" if (b)")
<< Line(" foo")
<< Line(" else if (c)")
<< Line(" foo")
<< Line(" else")
<< Line(" if (d)")
<< Line(" foo;")
<< Line(" else")
<< Line(" a + b + ")
<< Line(" c")
<< Line(" else")
<< Line(" foo;")
<< Line(" y: 2")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithoutBraces2()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" x: {")
<< 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(" e")
<< Line(" else")
<< Line(" foo;")
<< Line(" }")
<< Line(" foo: bar")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithBraces1()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line("onClicked: {", 4)
<< 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("}")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithBraces2()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line("onClicked:", 4)
<< 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("Rectangle {")
<< Line("onClicked:", 4)
<< 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("Rectangle {")
<< Line("onClicked: {", 4)
<< 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("}")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::ifStatementLongCondition()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line("onClicked: {", 4)
<< 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("Rectangle {")
<< Line("onClicked: {", 4)
<< Line(" while( true ) {}")
<< Line(" else", -1)
<< Line(" else {", -1)
<< Line(" }", -1)
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::oneLineIf()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" onClicked: { if (showIt) show(); }")
<< Line(" x: 2")
<< Line("};")
;
checkIndent(data);
}
void tst_CodeFormatter::forStatement()
{
QList<Line> data;
data << Line("for (var i = 0; i < 20; ++i) {")
<< Line(" print(i);")
<< Line("}")
<< Line("for (var x in [a, b, c, d])")
<< Line(" x += 5")
<< Line("var z")
<< Line("for (var x in [a, b, c, d])")
<< Line(" for (;;)")
<< Line(" {")
<< Line(" for (a(); b(); c())")
<< Line(" for (a();")
<< Line(" b(); c())")
<< Line(" for (a(); b(); c())")
<< Line(" print(3*d)")
<< Line(" }")
<< Line("z = 2")
;
checkIndent(data);
}
void tst_CodeFormatter::whileStatement()
{
QList<Line> data;
data << Line("while (i < 20) {")
<< Line(" print(i);")
<< Line("}")
<< Line("while (x in [a, b, c, d])")
<< Line(" x += 5")
<< Line("var z")
<< Line("while (a + b > 0")
<< Line(" && b + c > 0)")
<< Line(" for (;;)")
<< Line(" {")
<< Line(" for (a(); b(); c())")
<< Line(" while (a())")
<< Line(" for (a(); b(); c())")
<< Line(" print(3*d)")
<< Line(" }")
<< Line("z = 2")
;
checkIndent(data);
}
void tst_CodeFormatter::tryStatement()
{
QList<Line> data;
data << Line("try {")
<< Line(" print(i);")
<< Line("} catch (foo) {")
<< Line(" print(foo)")
<< Line("} finally {")
<< Line(" var z")
<< Line(" while (a + b > 0")
<< Line(" && b + c > 0)")
<< Line(" ;")
<< Line("}")
<< Line("z = 2")
;
checkIndent(data);
}
void tst_CodeFormatter::doWhile()
{
QList<Line> data;
data << Line("function 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::cStyleComments()
{
QList<Line> data;
data << Line("/*")
<< Line(" ")
<< Line(" foo")
<< Line(" ")
<< Line(" foo")
<< Line(" ")
<< Line("*/")
<< Line("Rectangle {")
<< Line(" /*")
<< Line(" ")
<< Line(" foo")
<< Line(" ")
<< Line(" */")
<< Line(" /* bar */")
<< Line("}")
<< Line("Item {")
<< Line(" /* foo */")
<< Line(" /*")
<< Line(" ")
<< Line(" foo")
<< Line(" ")
<< Line(" */")
<< Line(" /* bar */")
;
checkIndent(data);
}
void tst_CodeFormatter::cppStyleComments()
{
QList<Line> data;
data << Line("// abc")
<< Line("Item { ")
<< Line(" // ghij")
<< Line(" // def")
<< Line(" // ghij")
<< Line(" x: 4 // hik")
<< Line(" // doo")
<< Line("} // ba")
<< Line("// ba")
;
checkIndent(data);
}
void tst_CodeFormatter::ternary()
{
QList<Line> data;
data << Line("function foo() {")
<< Line(" var i = a ? b : c;")
<< Line(" foo += a_bigger_condition ?")
<< Line(" b")
<< Line(" : c;")
<< Line(" foo += a_bigger_condition")
<< Line(" ? b")
<< Line(" : c;")
<< Line(" var i = a ?")
<< Line(" b : c;")
<< Line(" var i = aooo ? b")
<< Line(" : c +")
<< Line(" 2;")
<< Line(" var i = (a ? b : c) + (foo")
<< Line(" bar);")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::switch1()
{
QList<Line> data;
data << Line("function 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::gnuStyle()
//{
// QList<Line> data;
// data << Line("struct S")
// << Line("{")
// << Line(" void foo()")
// << Line(" {")
// << Line(" if (a)")
// << Line(" {")
// << Line(" fpp;")
// << Line(" }")
// << Line(" else if (b)")
// << Line(" {")
// << Line(" fpp;")
// << Line(" }")
// << Line(" else")
// << Line(" {")
// << Line(" }")
// << Line(" if (b) {")
// << Line(" fpp;")
// << Line(" }")
// << Line(" }")
// << Line("};")
// ;
// checkIndent(data, 1);
//}
//void tst_CodeFormatter::whitesmithsStyle()
//{
// QList<Line> data;
// data << Line("struct S")
// << Line(" {")
// << Line(" void foo()")
// << Line(" {")
// << Line(" if (a)")
// << Line(" {")
// << Line(" fpp;")
// << Line(" }")
// << Line(" if (b) {")
// << Line(" fpp;")
// << Line(" }")
// << Line(" }")
// << Line(" };")
// ;
// checkIndent(data, 2);
//}
void tst_CodeFormatter::qmlKeywords()
{
QList<Line> data;
data << Line("Rectangle {")
<< Line(" on: 2")
<< Line(" property: 2")
<< Line(" signal: 2")
<< Line(" list: 2")
<< Line(" as: 2")
<< Line(" import: 2")
<< Line(" Item {")
<< Line(" }")
<< Line(" x: 2")
<< Line("};")
;
checkIndent(data);
}
void tst_CodeFormatter::expressionContinuation()
{
QList<Line> data;
data << Line("var x = 1 ? 2")
<< Line(" + 3 : 4")
<< Line("++x")
<< Line("++y--")
<< Line("x +=")
<< Line(" y++")
<< Line("var z")
;
checkIndent(data);
}
QTEST_APPLESS_MAIN(tst_CodeFormatter)
#include "tst_codeformatter.moc"

View File

@@ -1,3 +1,3 @@
TEMPLATE = subdirs TEMPLATE = subdirs
SUBDIRS += lookup SUBDIRS += lookup codeformatter