initial rev

This commit is contained in:
Vladimir Matveev 2014-10-18 15:40:00 -07:00
parent 6fe2b3ea90
commit 381a2ec425
2 changed files with 252 additions and 140 deletions

View File

@ -1,5 +1,6 @@
///<reference path='..\services.ts' />
///<reference path='stringUtilities.ts' />
///<reference path='formattingScanner.ts' />
///<reference path='new\rulesProvider.ts' />
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 {

View File

@ -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;
}
}
}