From 381a2ec4256c0175bd7918f942075947677bea96 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 18 Oct 2014 15:40:00 -0700 Subject: [PATCH] initial rev --- src/services/formatting/format.ts | 248 ++++++++----------- src/services/formatting/formattingScanner.ts | 144 +++++++++++ 2 files changed, 252 insertions(+), 140 deletions(-) create mode 100644 src/services/formatting/formattingScanner.ts diff --git a/src/services/formatting/format.ts b/src/services/formatting/format.ts index 7a620c6b1ab..2221674976a 100644 --- a/src/services/formatting/format.ts +++ b/src/services/formatting/format.ts @@ -1,5 +1,6 @@ /// /// +/// /// module ts.formatting { @@ -8,15 +9,12 @@ module ts.formatting { kind: SyntaxKind; } - export interface TokenInfo extends TextRange { + export interface TokenInfo { leadingTrivia: TextRangeWithKind[]; token: TextRangeWithKind; trailingTrivia: TextRangeWithKind[]; - pos: number; - end: number; } - var formattingScanner = createScanner(ScriptTarget.ES5, /*skipTrivia*/ false); export function formatOnEnter(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{ var line = getNonAdjustedLineAndCharacterFromPosition(position, sourceFile).line; @@ -27,7 +25,7 @@ module ts.formatting { // get end position for the current line (end value is exclusive so add 1 to the result) end: getEndLinePosition(line, sourceFile) + 1 } - return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnEnter, formattingScanner); + return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnEnter); } export function formatOnSemicolon(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{ @@ -43,7 +41,7 @@ module ts.formatting { pos: 0, end: sourceFile.text.length }; - return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatDocument, formattingScanner); + return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatDocument); } export function formatSelection(start: number, end: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{ @@ -52,7 +50,7 @@ module ts.formatting { pos: getStartLinePositionForPosition(start, sourceFile), end: end }; - return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatSelection, formattingScanner); + return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatSelection); } function getEndLinePosition(line: number, sourceFile: SourceFile): number { @@ -97,7 +95,7 @@ module ts.formatting { pos: getStartLinePositionForPosition(parent.pos, sourceFile), end: parent.end }; - return formatSpan(span, sourceFile, options, rulesProvider, requestKind, formattingScanner); + return formatSpan(span, sourceFile, options, rulesProvider, requestKind); } function findOutermostParent(position: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node { @@ -169,22 +167,11 @@ module ts.formatting { return { line: lineAndChar.line - 1, character: lineAndChar.character - 1 }; } - function rescanIfNecessary(scanner: Scanner, parent: Node): void { - var t = scanner.getToken(); - if (parent.kind === SyntaxKind.BinaryExpression && t === SyntaxKind.GreaterThanToken) { - scanner.reScanGreaterToken(); - } - else if (parent.kind === SyntaxKind.RegularExpressionLiteral && t === SyntaxKind.SlashToken) { - scanner.reScanSlashToken(); - } - } - function formatSpan(originalRange: TextRange, sourceFile: SourceFile, options: FormatCodeOptions, rulesProvider: RulesProvider, - requestKind: FormattingRequestKind, - scanner: Scanner): TextChange[] { + requestKind: FormattingRequestKind): TextChange[] { // formatting context to be used by rules provider to get rules var formattingContext = new FormattingContext(sourceFile, requestKind); @@ -192,21 +179,18 @@ module ts.formatting { var enclosingNode = findEnclosingNode(originalRange, sourceFile); var initialIndentation = getIndentationForNode(enclosingNode, sourceFile, options); - scanner.setText(sourceFile.text); - scanner.setTextPos(enclosingNode.pos); + var formattingScanner = getFormattingScanner(sourceFile, enclosingNode, originalRange); var previousRange: TextRangeWithKind; var previousParent: Node; var previousRangeStartLine: number; - var lastTriviaWasNewLine = true; var edits: TextChange[] = []; + var lastTriviaWasNewLine: boolean; - scanner.scan(); + formattingScanner.advance(); - var currentTokenInfo = fetchNextTokenInfo(enclosingNode); - - if (currentTokenInfo.token) { + if (formattingScanner.hasToken()) { var startLine = getNonAdjustedLineAndCharacterFromPosition(enclosingNode.getStart(sourceFile), sourceFile).line; processNode(enclosingNode, enclosingNode, startLine, initialIndentation); } @@ -218,11 +202,6 @@ module ts.formatting { return; } - if (!rangeContainsRange(node, currentTokenInfo.token)) { - // node and its descendents don't contain current token from the scanner - skip it - return; - } - var childContextNode = contextNode; forEachChild( node, @@ -234,15 +213,27 @@ module ts.formatting { } ); - while (currentTokenInfo.token && node.end >= currentTokenInfo.token.end) { - if (SmartIndenter.nodeContentIsAlwaysIndented(node)) { - currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); + // this eats up last tokens in the node + // TODO: resync token info and consume it + while (formattingScanner.hasToken()) { + var tokenInfo = formattingScanner.consumeTokenAndTrailingTrivia(node); + if (node.end >= tokenInfo.token.end) { + consumeTokenAndAdvance(tokenInfo, node, childContextNode, indentation); + childContextNode = node; } else { - currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); + break; } - childContextNode = node; } + //while (currentTokenInfo.token && node.end >= currentTokenInfo.token.end) { + // if (SmartIndenter.nodeContentIsAlwaysIndented(node)) { + // currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); + // } + // else { + // currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); + // } + // childContextNode = node; + //} /// Local functions @@ -252,123 +243,101 @@ module ts.formatting { } var start = child.getStart(sourceFile); - - while (currentTokenInfo.token && start >= currentTokenInfo.token.end) { - // we've walked past the current token - // ask parent to handle it - currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); - childContextNode = node; + while (formattingScanner.hasToken()) { + var tokenInfo = formattingScanner.consumeTokenAndTrailingTrivia(node); + if (start >= tokenInfo.token.end) { + consumeTokenAndAdvance(tokenInfo, node, childContextNode, indentation); + } + else { + break; + } } - if (!currentTokenInfo.token) { + //while (currentTokenInfo.token && start >= currentTokenInfo.token.end) { + // // we've walked past the current token + // // ask parent to handle it + // currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); + // childContextNode = node; + //} + + if (!formattingScanner.hasToken()) { return; } + //if (!currentTokenInfo.token) { + // return; + //} + // ensure that current token is inside child node - Debug.assert(currentTokenInfo.token.end <= child.end); - if (isToken(child) && currentTokenInfo.token.end === child.end) { - // tokens belong to parent nodes - currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); - childContextNode = node; + if (isToken(child)) { + var tokenInfo = formattingScanner.consumeTokenAndTrailingTrivia(node); + if (tokenInfo.token.end === child.end) { + consumeTokenAndAdvance(tokenInfo, node, childContextNode, indentation); + childContextNode = node; + return; + } + } + + var childStartLine = getNonAdjustedLineAndCharacterFromPosition(start, sourceFile).line; + + var childIndentation = indentation; + if (listElementIndex === -1) { + // child is not list element + } else { - var childStartLine = getNonAdjustedLineAndCharacterFromPosition(start, sourceFile).line; - - var childIndentation = indentation; - if (listElementIndex === -1) { - // child is not list element - - } - else { - // child is a list element - } - // determine child indentation - // if child - // TODO: share this code with SmartIndenter - var increaseIndentation = - childStartLine !== nodeStartLine && - !SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(node, child, childStartLine, sourceFile) && - SmartIndenter.shouldIndentChildNode(node, child); - - processNode(child, childContextNode, childStartLine, increaseIndentation ? indentation + options.IndentSize : indentation); - childContextNode = node; + // child is a list element } + // determine child indentation + // if child + // TODO: share this code with SmartIndenter + var increaseIndentation = + childStartLine !== nodeStartLine && + !SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(node, child, childStartLine, sourceFile) && + SmartIndenter.shouldIndentChildNode(node, child); + + processNode(child, childContextNode, childStartLine, increaseIndentation ? indentation + options.IndentSize : indentation); + childContextNode = node; + + //if (isToken(child) && currentTokenInfo.token.end === child.end) { + // // tokens belong to parent nodes + // currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation); + // childContextNode = node; + //} + //else { + // var childStartLine = getNonAdjustedLineAndCharacterFromPosition(start, sourceFile).line; + + // var childIndentation = indentation; + // if (listElementIndex === -1) { + // // child is not list element + + // } + // else { + // // child is a list element + // } + // // determine child indentation + // // if child + // // TODO: share this code with SmartIndenter + // var increaseIndentation = + // childStartLine !== nodeStartLine && + // !SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(node, child, childStartLine, sourceFile) && + // SmartIndenter.shouldIndentChildNode(node, child); + + // processNode(child, childContextNode, childStartLine, increaseIndentation ? indentation + options.IndentSize : indentation); + // childContextNode = node; + //} } } - function fetchNextTokenInfo(parent: Node): TokenInfo { - if (currentTokenInfo) { - var trivia = currentTokenInfo.trailingTrivia; - lastTriviaWasNewLine = - trivia && - trivia[trivia.length - 1].kind === SyntaxKind.NewLineTrivia; - } - - var leadingTrivia: TextRangeWithKind[]; - var trailingTrivia: TextRangeWithKind[]; - var tokenRange: TextRangeWithKind; - - var startPos = scanner.getStartPos(); - var initialStartPos = startPos; - - while (startPos < originalRange.end) { - rescanIfNecessary(scanner, parent); - - var t = scanner.getToken(); - - if (tokenRange && !isTrivia(t)) { - // have already seen the token and item under cursor is not a trivia - break; - } - - scanner.scan(); - - var item = { pos: startPos, end: scanner.getStartPos(), kind: t }; - startPos = item.end; - - if (isTrivia(t)) { - if (tokenRange) { - - if (!trailingTrivia) { - trailingTrivia = []; - } - - trailingTrivia.push(item); - - if (t === SyntaxKind.NewLineTrivia) { - // trailing trivia is cut at the new line - break; - } - } - else { - if (!leadingTrivia) { - leadingTrivia = []; - } - - leadingTrivia.push(item); - } - } - else { - tokenRange = item; - } - } - - return { - leadingTrivia: leadingTrivia, - token: tokenRange, - trailingTrivia: trailingTrivia, - pos: initialStartPos, - end: scanner.getStartPos() - }; - } - - function consumeCurrentToken(parent: Node, contextNode: Node, indentation: number): TokenInfo { + function consumeTokenAndAdvance(currentTokenInfo: TokenInfo, parent: Node, contextNode: Node, indentation: number): void { Debug.assert(rangeContainsRange(parent, currentTokenInfo.token)); + + lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine(); + if (currentTokenInfo.leadingTrivia) { processTrivia(currentTokenInfo.leadingTrivia, parent, contextNode, indentation); } - var indentToken: boolean; if (rangeContainsRange(originalRange, currentTokenInfo.token)) { indentToken = processRange(currentTokenInfo.token, parent, contextNode, indentation); @@ -378,7 +347,7 @@ module ts.formatting { processTrivia(currentTokenInfo.trailingTrivia, parent, contextNode, indentation); } - if (lastTriviaWasNewLine && indentToken) { + if (formattingScanner.lastTrailingTriviaWasNewLine() && indentToken) { var indentNextTokenOrTrivia = true; if (currentTokenInfo.leadingTrivia) { for (var i = 0, len = currentTokenInfo.leadingTrivia.length; i < len; ++i) { @@ -419,8 +388,7 @@ module ts.formatting { //} } - - return fetchNextTokenInfo(parent); + formattingScanner.advance(); } function insertIndentation(pos: number, indentation: number): void { diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts new file mode 100644 index 00000000000..8a988a9485e --- /dev/null +++ b/src/services/formatting/formattingScanner.ts @@ -0,0 +1,144 @@ +module ts.formatting { + var scanner = createScanner(ScriptTarget.ES5, /*skipTrivia*/ false); + + export interface FormattingScanner { + advance(): void; + hasToken(): boolean; + consumeTokenAndTrailingTrivia(n: Node): TokenInfo; + lastTrailingTriviaWasNewLine(): boolean; + } + + export function getFormattingScanner(sourceFile: SourceFile, enclosingNode: Node, range: TextRange): FormattingScanner { + + scanner.setText(sourceFile.text); + scanner.setTextPos(enclosingNode.pos); + + return { + advance: advance, + consumeTokenAndTrailingTrivia: consumeTokenAndTrailingTrivia, + hasToken: hasToken, + lastTrailingTriviaWasNewLine: lastTrailingTriviaWasNewLine + } + + var leadingTrivia: TextRangeWithKind[]; + var trailingTrivia: TextRangeWithKind[]; + var token: TextRangeWithKind; + var wasNewLine: boolean = true; + var savedStartPos: number; + + function advance(): void { + // accumulate leading trivia and token + if (trailingTrivia) { + Debug.assert(trailingTrivia.length); + wasNewLine = trailingTrivia[trailingTrivia.length - 1].kind === SyntaxKind.NewLineTrivia; + } + + leadingTrivia = undefined; + trailingTrivia = undefined; + token = undefined; + + if (scanner.getStartPos() === enclosingNode.pos) { + scanner.scan(); + } + + var t: SyntaxKind; + var startPos = scanner.getStartPos(); + + while (startPos < range.end) { + var t = scanner.getToken(); + if (token && !isTrivia(t)) { + break; + } + + // advance to the next token + scanner.scan(); + + var item = { + pos: startPos, + end: scanner.getStartPos(), + kind: t + }; + + startPos = scanner.getStartPos(); + + if (isTrivia(t)) { + if (token) { + break; + } + else { + if (!leadingTrivia) { + leadingTrivia = []; + } + leadingTrivia.push(item); + } + } + else { + token = item; + } + } + + savedStartPos = scanner.getStartPos(); + } + + function consumeTokenAndTrailingTrivia(n: Node): TokenInfo { + Debug.assert(hasToken()); + if (scanner.getStartPos() !== savedStartPos) { + scanner.setTextPos(savedStartPos) + } + + if (n.kind === SyntaxKind.BinaryExpression && token.kind === SyntaxKind.GreaterThanToken) { + scanner.setTextPos(token.pos); + scanner.scan(); + token.kind = scanner.reScanGreaterToken(); + token.end = scanner.getTextPos(); + scanner.scan(); + } + else if (n.kind === SyntaxKind.RegularExpressionLiteral && token.kind === SyntaxKind.SlashToken) { + scanner.setTextPos(token.pos); + scanner.scan(); + token.kind = scanner.reScanSlashToken(); + token.end = scanner.getTextPos(); + scanner.scan(); + } + + // scan trailing trivia + var startPos = scanner.getStartPos(); + while (startPos < range.end) { + var t = scanner.getToken(); + + if (isTrivia(t) && t !== SyntaxKind.NewLineTrivia) { + if (!trailingTrivia) { + trailingTrivia = []; + } + + var trivia = { + pos: startPos, + end: scanner.getStartPos(), + kind: t + } + trailingTrivia.push(trivia); + } + else { + break; + } + scanner.scan(); + startPos = scanner.getStartPos(); + } + + return { + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia, + token: token + } + } + + function hasToken(): boolean { + return token !== undefined; + } + + function lastTrailingTriviaWasNewLine(): boolean { + return wasNewLine; + } + } + +} \ No newline at end of file