1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
33 #include "qmljscodeformatter.h"
35 #include <QtCore/QDebug>
36 #include <QtCore/QMetaEnum>
37 #include <QtGui/QTextDocument>
38 #include <QtGui/QTextCursor>
39 #include <QtGui/QTextBlock>
41 using namespace QmlJS;
43 CodeFormatter::BlockData::BlockData()
49 CodeFormatter::CodeFormatter()
55 CodeFormatter::~CodeFormatter()
59 void CodeFormatter::setTabSize(int tabSize)
64 void CodeFormatter::recalculateStateAfter(const QTextBlock &block)
66 restoreCurrentState(block.previous());
68 const int lexerState = tokenizeBlock(block);
72 //qDebug() << "Starting to look at " << block.text() << block.blockNumber() + 1;
74 for (; m_tokenIndex < m_tokens.size(); ) {
75 m_currentToken = tokenAt(m_tokenIndex);
76 const int kind = extendedTokenKind(m_currentToken);
78 //qDebug() << "Token" << m_currentLine.mid(m_currentToken.begin(), m_currentToken.length) << m_tokenIndex << "in line" << block.blockNumber() + 1;
82 && state().type != multiline_comment_cont
83 && state().type != multiline_comment_start) {
88 switch (m_currentState.top().type) {
91 case Identifier: enter(objectdefinition_or_js); continue;
92 case Import: enter(top_qml); continue;
93 default: enter(top_js); continue;
98 case Import: enter(import_start); break;
99 case Identifier: enter(binding_or_objectdefinition); break;
106 case objectdefinition_or_js:
110 if (!m_currentLine.at(m_currentToken.begin()).isUpper()) {
115 case LeftBrace: turnInto(binding_or_objectdefinition); continue;
116 default: turnInto(top_js); continue;
120 enter(import_maybe_dot_or_version_or_as);
123 case import_maybe_dot_or_version_or_as:
125 case Dot: turnInto(import_dot); break;
126 case As: turnInto(import_as); break;
127 case Number: turnInto(import_maybe_as); break;
128 default: leave(); leave(); continue;
131 case import_maybe_as:
133 case As: turnInto(import_as); break;
134 default: leave(); leave(); continue;
139 case Identifier: turnInto(import_maybe_dot_or_version_or_as); break;
140 default: leave(); leave(); continue;
145 case Identifier: leave(); leave(); break;
148 case binding_or_objectdefinition:
150 case Colon: enter(binding_assignment); break;
151 case LeftBrace: enter(objectdefinition_open); break;
154 case binding_assignment:
156 case Semicolon: leave(true); break;
157 case If: enter(if_statement); break;
158 case LeftBrace: enter(jsblock_open); break;
165 case Identifier: enter(expression_or_objectdefinition); break;
166 default: enter(expression); continue;
169 case objectdefinition_open:
171 case RightBrace: leave(true); break;
172 case Default: enter(default_property_start); break;
173 case Property: enter(property_start); break;
174 case Function: enter(function_start); break;
175 case Signal: enter(signal_start); break;
180 case Identifier: enter(binding_or_objectdefinition); break;
183 case default_property_start:
184 if (kind != Property)
187 turnInto(property_start);
192 case Colon: enter(binding_assignment); break; // oops, was a binding
194 case Identifier: enter(property_type); break;
195 case List: enter(property_list_open); break;
196 default: leave(true); continue;
200 turnInto(property_maybe_initializer);
203 case property_list_open:
204 if (m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length) == QLatin1String(">"))
205 turnInto(property_maybe_initializer);
208 case property_maybe_initializer:
210 case Colon: turnInto(binding_assignment); break;
211 default: leave(true); continue;
216 case Colon: enter(binding_assignment); break; // oops, was a binding
217 default: enter(signal_maybe_arglist); break;
220 case signal_maybe_arglist:
222 case LeftParenthesis: turnInto(signal_arglist_open); break;
223 default: leave(true); continue;
226 case signal_arglist_open:
228 case RightParenthesis: leave(true); break;
233 case LeftParenthesis: enter(function_arglist_open); break;
236 case function_arglist_open:
238 case RightParenthesis: turnInto(function_arglist_closed); break;
241 case function_arglist_closed:
243 case LeftBrace: turnInto(jsblock_open); break;
244 default: leave(true); continue; // error recovery
247 case expression_or_objectdefinition:
250 case Identifier: break; // need to become an objectdefinition_open in cases like "width: Qt.Foo {"
251 case LeftBrace: turnInto(objectdefinition_open); break;
252 default: enter(expression); continue; // really? identifier and more tokens might already be gone
255 case expression_or_label:
257 case Colon: turnInto(labelled_statement); break;
258 default: enter(expression); continue;
263 enter(ternary_op_after_colon);
264 enter(expression_continuation);
268 case ternary_op_after_colon:
270 if (tryInsideExpression())
274 case Delimiter: enter(expression_continuation); break;
276 case RightParenthesis: leave(); continue;
277 case RightBrace: leave(true); continue;
278 case Semicolon: leave(true); break;
281 case expression_continuation:
285 case expression_maybe_continuation:
290 case LeftParenthesis: leave(); continue;
291 default: leave(true); continue;
295 if (tryInsideExpression())
298 case RightParenthesis: leave(); break;
302 if (tryInsideExpression())
305 case Comma: enter(bracket_element_start); break;
306 case RightBracket: leave(); break;
309 case objectliteral_open:
310 if (tryInsideExpression())
313 case Colon: enter(objectliteral_assignment); break;
315 case RightParenthesis: leave(); continue; // error recovery
316 case RightBrace: leave(true); break;
319 // pretty much like expression, but ends with , or }
320 case objectliteral_assignment:
321 if (tryInsideExpression())
324 case Delimiter: enter(expression_continuation); break;
326 case RightParenthesis: leave(); continue; // error recovery
327 case RightBrace: leave(); continue; // so we also leave objectliteral_open
328 case Comma: leave(); break;
331 case bracket_element_start:
333 case Identifier: turnInto(bracket_element_maybe_objectdefinition); break;
334 default: leave(); continue;
337 case bracket_element_maybe_objectdefinition:
339 case LeftBrace: turnInto(objectdefinition_open); break;
340 default: leave(); continue;
344 case substatement_open:
348 case RightBrace: leave(true); break;
351 case labelled_statement:
354 leave(true); // error recovery
358 // prefer substatement_open over block_open
359 if (kind != LeftBrace) {
364 case LeftBrace: turnInto(substatement_open); break;
369 case LeftParenthesis: enter(condition_open); break;
370 default: leave(true); break; // error recovery
375 turnInto(else_clause);
384 // ### shouldn't happen
392 case RightParenthesis: turnInto(substatement); break;
393 case LeftParenthesis: enter(condition_paren_open); break;
397 case condition_paren_open:
399 case RightParenthesis: leave(); break;
400 case LeftParenthesis: enter(condition_paren_open); break;
403 case switch_statement:
404 case statement_with_condition:
406 case LeftParenthesis: enter(statement_with_condition_paren_open); break;
407 default: leave(true);
410 case statement_with_condition_paren_open:
411 if (tryInsideExpression())
414 case RightParenthesis: turnInto(substatement); break;
417 case statement_with_block:
419 case LeftBrace: enter(jsblock_open); break;
420 default: leave(true); break;
426 case LeftParenthesis: enter(do_statement_while_paren_open); break;
427 default: leave(true); break;
430 case do_statement_while_paren_open:
431 if (tryInsideExpression())
434 case RightParenthesis: leave(); leave(true); break;
437 case breakcontinue_statement:
439 case Identifier: leave(true); break;
440 default: leave(true); continue; // try again
445 case Colon: turnInto(case_cont); break;
449 if (kind != Case && kind != Default && tryStatement())
452 case RightBrace: leave(); continue;
454 case Case: leave(); continue;
457 case multiline_comment_start:
458 case multiline_comment_cont:
459 if (kind != Comment) {
462 } else if (m_tokenIndex == m_tokens.size() - 1
463 && (lexerState & Scanner::MultiLineMask) == Scanner::Normal) {
465 } else if (m_tokenIndex == 0) {
466 // to allow enter/leave to update the indentDepth
467 turnInto(multiline_comment_cont);
472 qWarning() << "Unhandled state" << m_currentState.top().type;
474 } // end of state switch
479 int topState = m_currentState.top().type;
481 // if there's no colon on the same line, it's not a label
482 if (topState == expression_or_label)
484 // if not followed by an identifier on the same line, it's done
485 else if (topState == breakcontinue_statement)
488 topState = m_currentState.top().type;
490 // some states might be continued on the next line
491 if (topState == expression
492 || topState == expression_or_objectdefinition
493 || topState == objectliteral_assignment
494 || topState == ternary_op_after_colon) {
495 enter(expression_maybe_continuation);
497 // multi-line comment start?
498 if (topState != multiline_comment_start
499 && topState != multiline_comment_cont
500 && (lexerState & Scanner::MultiLineMask) == Scanner::MultiLineComment) {
501 enter(multiline_comment_start);
504 saveCurrentState(block);
507 int CodeFormatter::indentFor(const QTextBlock &block)
509 // qDebug() << "indenting for" << block.blockNumber() + 1;
511 restoreCurrentState(block.previous());
512 correctIndentation(block);
513 return m_indentDepth;
516 int CodeFormatter::indentForNewLineAfter(const QTextBlock &block)
518 restoreCurrentState(block);
520 int lexerState = loadLexerState(block);
522 m_currentLine.clear();
523 adjustIndent(m_tokens, lexerState, &m_indentDepth);
525 return m_indentDepth;
528 void CodeFormatter::updateStateUntil(const QTextBlock &endBlock)
530 QStack<State> previousState = initialState();
531 QTextBlock it = endBlock.document()->firstBlock();
533 // find the first block that needs recalculation
534 for (; it.isValid() && it != endBlock; it = it.next()) {
536 if (!loadBlockData(it, &blockData))
538 if (blockData.m_blockRevision != it.revision())
540 if (previousState != blockData.m_beginState)
542 if (loadLexerState(it) == -1)
545 previousState = blockData.m_endState;
551 // update everthing until endBlock
552 for (; it.isValid() && it != endBlock; it = it.next()) {
553 recalculateStateAfter(it);
556 // invalidate everything below by marking the state in endBlock as invalid
558 BlockData invalidBlockData;
559 saveBlockData(&it, invalidBlockData);
563 void CodeFormatter::updateLineStateChange(const QTextBlock &block)
565 if (!block.isValid())
569 if (loadBlockData(block, &blockData) && blockData.m_blockRevision == block.revision())
572 recalculateStateAfter(block);
574 // invalidate everything below by marking the next block's state as invalid
575 QTextBlock next = block.next();
579 saveBlockData(&next, BlockData());
582 CodeFormatter::State CodeFormatter::state(int belowTop) const
584 if (belowTop < m_currentState.size())
585 return m_currentState.at(m_currentState.size() - 1 - belowTop);
590 const QVector<CodeFormatter::State> &CodeFormatter::newStatesThisLine() const
595 int CodeFormatter::tokenIndex() const
600 int CodeFormatter::tokenCount() const
602 return m_tokens.size();
605 const Token &CodeFormatter::currentToken() const
607 return m_currentToken;
610 void CodeFormatter::invalidateCache(QTextDocument *document)
615 BlockData invalidBlockData;
616 QTextBlock it = document->firstBlock();
617 for (; it.isValid(); it = it.next()) {
618 saveBlockData(&it, invalidBlockData);
622 void CodeFormatter::enter(int newState)
624 int savedIndentDepth = m_indentDepth;
625 onEnter(newState, &m_indentDepth, &savedIndentDepth);
626 State s(newState, savedIndentDepth);
627 m_currentState.push(s);
630 if (newState == bracket_open)
631 enter(bracket_element_start);
634 void CodeFormatter::leave(bool statementDone)
636 Q_ASSERT(m_currentState.size() > 1);
637 if (m_currentState.top().type == topmost_intro)
640 if (m_newStates.size() > 0)
643 // restore indent depth
644 State poppedState = m_currentState.pop();
645 m_indentDepth = poppedState.savedIndentDepth;
647 int topState = m_currentState.top().type;
649 // if statement is done, may need to leave recursively
651 if (!isExpressionEndState(topState))
653 if (topState == if_statement) {
654 if (poppedState.type != maybe_else)
658 } else if (topState == else_clause) {
659 // leave the else *and* the surrounding if, to prevent another else
666 void CodeFormatter::correctIndentation(const QTextBlock &block)
668 const int lexerState = tokenizeBlock(block);
669 Q_ASSERT(m_currentState.size() >= 1);
671 adjustIndent(m_tokens, lexerState, &m_indentDepth);
674 bool CodeFormatter::tryInsideExpression(bool alsoExpression)
677 const int kind = extendedTokenKind(m_currentToken);
679 case LeftParenthesis: newState = paren_open; break;
680 case LeftBracket: newState = bracket_open; break;
681 case LeftBrace: newState = objectliteral_open; break;
682 case Function: newState = function_start; break;
683 case Question: newState = ternary_op; break;
686 if (newState != -1) {
696 bool CodeFormatter::tryStatement()
698 const int kind = extendedTokenKind(m_currentToken);
701 enter(empty_statement);
706 enter(breakcontinue_statement);
709 enter(throw_statement);
713 enter(return_statement);
719 enter(statement_with_condition);
722 enter(switch_statement);
737 enter(statement_with_block);
743 enter(expression_or_label);
759 // look at the token again
766 bool CodeFormatter::isBracelessState(int type) const
769 type == if_statement ||
770 type == else_clause ||
771 type == substatement ||
772 type == binding_assignment ||
773 type == binding_or_objectdefinition;
776 bool CodeFormatter::isExpressionEndState(int type) const
779 type == topmost_intro ||
781 type == objectdefinition_open ||
782 type == if_statement ||
783 type == else_clause ||
784 type == jsblock_open ||
785 type == substatement_open ||
786 type == bracket_open ||
787 type == paren_open ||
789 type == objectliteral_open;
792 const Token &CodeFormatter::tokenAt(int idx) const
794 static const Token empty;
795 if (idx < 0 || idx >= m_tokens.size())
798 return m_tokens.at(idx);
801 int CodeFormatter::column(int index) const
804 if (index > m_currentLine.length())
805 index = m_currentLine.length();
807 const QChar tab = QLatin1Char('\t');
809 for (int i = 0; i < index; i++) {
810 if (m_currentLine[i] == tab) {
811 col = ((col / m_tabSize) + 1) * m_tabSize;
819 QStringRef CodeFormatter::currentTokenText() const
821 return m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length);
824 void CodeFormatter::turnInto(int newState)
830 void CodeFormatter::saveCurrentState(const QTextBlock &block)
832 if (!block.isValid())
836 blockData.m_blockRevision = block.revision();
837 blockData.m_beginState = m_beginState;
838 blockData.m_endState = m_currentState;
839 blockData.m_indentDepth = m_indentDepth;
841 QTextBlock saveableBlock(block);
842 saveBlockData(&saveableBlock, blockData);
845 void CodeFormatter::restoreCurrentState(const QTextBlock &block)
847 if (block.isValid()) {
849 if (loadBlockData(block, &blockData)) {
850 m_indentDepth = blockData.m_indentDepth;
851 m_currentState = blockData.m_endState;
852 m_beginState = m_currentState;
857 m_currentState = initialState();
858 m_beginState = m_currentState;
862 QStack<CodeFormatter::State> CodeFormatter::initialState()
864 static QStack<CodeFormatter::State> initialState;
865 if (initialState.isEmpty())
866 initialState.push(State(topmost_intro, 0));
870 int CodeFormatter::tokenizeBlock(const QTextBlock &block)
872 int startState = loadLexerState(block.previous());
873 if (block.blockNumber() == 0)
875 Q_ASSERT(startState != -1);
878 tokenize.setScanComments(true);
880 m_currentLine = block.text();
881 // to determine whether a line was joined, Tokenizer needs a
882 // newline character at the end
883 m_currentLine.append(QLatin1Char('\n'));
884 m_tokens = tokenize(m_currentLine, startState);
886 const int lexerState = tokenize.state();
887 QTextBlock saveableBlock(block);
888 saveLexerState(&saveableBlock, lexerState);
892 CodeFormatter::TokenKind CodeFormatter::extendedTokenKind(const QmlJS::Token &token) const
894 const int kind = token.kind;
895 QStringRef text = m_currentLine.midRef(token.begin(), token.length);
897 if (kind == Identifier) {
900 if (text == "import")
902 if (text == "signal")
904 if (text == "property")
910 } else if (kind == Keyword) {
911 const QChar char1 = text.at(0);
912 const QChar char2 = text.at(1);
913 const QChar char3 = (text.size() > 2 ? text.at(2) : QChar());
914 switch (char1.toLatin1()) {
920 else if (char3 == 's')
927 else if (char2 == 'u')
968 } else if (kind == Delimiter) {
971 else if (text == "++")
973 else if (text == "--")
977 return static_cast<TokenKind>(kind);
980 void CodeFormatter::dump() const
982 QMetaEnum metaEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType"));
984 qDebug() << "Current token index" << m_tokenIndex;
985 qDebug() << "Current state:";
986 foreach (const State &s, m_currentState) {
987 qDebug() << metaEnum.valueToKey(s.type) << s.savedIndentDepth;
989 qDebug() << "Current indent depth:" << m_indentDepth;