change folder structure, move all new formatting related bits to 'format' folder

This commit is contained in:
Vladimir Matveev 2014-11-06 15:31:42 -08:00
parent c3a88d3482
commit b7b3506c59
22 changed files with 48 additions and 452 deletions

View File

@ -1,8 +1,7 @@
///<reference path='..\services.ts' />
///<reference path='stringUtilities.ts' />
///<reference path='lineMapUtilities.ts' />
///<reference path='formattingScanner.ts' />
///<reference path='new\rulesProvider.ts' />
///<reference path='services.ts' />
///<reference path='format\indentation.ts' />
///<reference path='format\formattingScanner.ts' />
///<reference path='format\rulesProvider.ts' />
module ts.formatting {
@ -25,10 +24,10 @@ module ts.formatting {
getEffectiveCommentIndentation(commentLine: number): number;
getDelta(): number;
getIndentation(): number;
setDelta(delta: number): number;
getCommentIndentation(): number;
increaseCommentIndentation(delta: number): void;
recomputeIndentation(lineAddedByFormatting: boolean): void;
setDelta(delta: number): number;
}
export function formatOnEnter(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
///<reference path='..\..\services.ts' />
///<reference path='..\services.ts' />
///<reference path='formattingContext.ts' />
///<reference path='formattingRequestKind.ts' />
///<reference path='rule.ts' />

View File

@ -1,3 +1,5 @@
/// <reference path="..\..\compiler\scanner.ts"/>
module ts.formatting {
var scanner = createScanner(ScriptTarget.ES5, /*skipTrivia*/ false);

View File

@ -1,41 +0,0 @@
///<reference path='..\..\compiler\types.ts' />
module ts.formatting {
export function getEndLinePosition(line: number, sourceFile: SourceFile): number {
Debug.assert(line >= 1);
var lineStarts = sourceFile.getLineStarts();
line = line - 1;
if (line === lineStarts.length - 1) {
// last line - return EOF
return sourceFile.text.length - 1;
}
else {
// current line start
var start = lineStarts[line];
// take the start position of the next line -1 = it should be some line break
var pos = lineStarts[line + 1] - 1;
Debug.assert(isLineBreak(sourceFile.text.charCodeAt(pos)));
// walk backwards skipping line breaks, stop the the beginning of current line.
// i.e:
// <some text>
// $ <- end of line for this position should match the start position
while (start <= pos && isLineBreak(sourceFile.text.charCodeAt(pos))) {
pos--;
}
return pos;
}
}
export function getStartPositionOfLine(line: number, sourceFile: SourceFile): number {
Debug.assert(line >= 1);
return sourceFile.getLineStarts()[line - 1];
}
export function getStartLinePositionForPosition(position: number, sourceFile: SourceFile): number {
var lineStarts = sourceFile.getLineStarts();
var line = sourceFile.getLineAndCharacterFromPosition(position).line;
return lineStarts[line - 1];
}
}

View File

@ -1,401 +0,0 @@
///<reference path='..\services.ts' />
///<reference path='..\servicesSyntaxUtilities.ts' />
module ts.formatting {
export module SmartIndenter {
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorOptions): number {
if (position > sourceFile.text.length) {
return 0; // past EOF
}
var precedingToken = ServicesSyntaxUtilities.findPrecedingToken(position, sourceFile);
if (!precedingToken) {
return 0;
}
// no indentation in string \regex literals
if ((precedingToken.kind === SyntaxKind.StringLiteral || precedingToken.kind === SyntaxKind.RegularExpressionLiteral) &&
precedingToken.getStart(sourceFile) <= position &&
precedingToken.end > position) {
return 0;
}
var lineAtPosition = sourceFile.getLineAndCharacterFromPosition(position).line;
if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) {
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
var actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options);
if (actualIndentation !== -1) {
return actualIndentation;
}
}
// try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken'
// if such node is found - compute initial indentation for 'position' inside this node
var previous: Node;
var current = precedingToken;
var currentStart: LineAndCharacter;
var indentationDelta: number;
while (current) {
if (positionBelongsToNode(current, position, sourceFile) && nodeContentIsIndented(current, previous)) {
currentStart = getStartLineAndCharacterForNode(current, sourceFile);
if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {
indentationDelta = 0;
}
else {
indentationDelta = lineAtPosition !== currentStart.line ? options.IndentSize : 0;
}
break;
}
// check if current node is a list item - if yes, take indentation from it
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
if (actualIndentation !== -1) {
return actualIndentation;
}
previous = current;
current = current.parent;
}
if (!current) {
// no parent was found - return 0 to be indented on the level of SourceFile
return 0;
}
return getIndentationForNode(current, currentStart, indentationDelta, sourceFile, options);
}
export function getIndentationForNode(current: Node, currentStart: LineAndCharacter, indentationDelta: number, sourceFile: SourceFile, options: EditorOptions): number {
var parent: Node = current.parent;
var parentStart: LineAndCharacter;
// walk upwards and collect indentations for pairs of parent-child nodes
// indentation is not added if parent and child nodes start on the same line or if parent is IfStatement and child starts on the same line with 'else clause'
while (parent) {
// check if current node is a list item - if yes, take indentation from it
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
if (actualIndentation !== -1) {
return actualIndentation + indentationDelta;
}
parentStart = sourceFile.getLineAndCharacterFromPosition(parent.getStart(sourceFile));
var parentAndChildShareLine =
parentStart.line === currentStart.line ||
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile);
// try to fetch actual indentation for current node from source text
var actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options);
if (actualIndentation !== -1) {
return actualIndentation + indentationDelta;
}
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
if (nodeContentIsIndented(parent, current) && !parentAndChildShareLine) {
indentationDelta += options.IndentSize;
}
current = parent;
currentStart = parentStart;
parent = current.parent;
}
return indentationDelta;
}
/*
* Function returns -1 if indentation cannot be determined
*/
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorOptions): number {
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
var commaItemInfo = ServicesSyntaxUtilities.findListItemInfo(commaToken);
Debug.assert(commaItemInfo.listItemIndex > 0);
// The item we're interested in is right before the comma
return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options);
}
/*
* Function returns -1 if actual indentation for node should not be used (i.e because node is nested expression)
*/
function getActualIndentationForNode(current: Node,
parent: Node,
currentLineAndChar: LineAndCharacter,
parentAndChildShareLine: boolean,
sourceFile: SourceFile,
options: EditorOptions): number {
// actual indentation is used for statements\declarations if one of cases below is true:
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
// - parent and child are not on the same line
var useActualIndentation =
(isDeclaration(current) || isStatement(current)) &&
(parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine);
if (!useActualIndentation) {
return -1;
}
return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options);
}
function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): boolean {
var nextToken = ServicesSyntaxUtilities.findNextToken(precedingToken, current);
if (!nextToken) {
return false;
}
if (nextToken.kind === SyntaxKind.OpenBraceToken) {
// open braces are always indented at the parent level
return true;
}
else if (nextToken.kind === SyntaxKind.CloseBraceToken) {
// close braces are indented at the parent level if they are located on the same line with cursor
// this means that if new line will be added at $ position, this case will be indented
// class A {
// $
// }
/// and this one - not
// class A {
// $}
var nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line;
return lineAtPosition === nextTokenStartLine;
}
return false;
}
function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter {
return sourceFile.getLineAndCharacterFromPosition(n.getStart(sourceFile));
}
function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
}
function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFile): boolean {
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
var elseKeyword = forEach(parent.getChildren(), c => c.kind === SyntaxKind.ElseKeyword && c);
Debug.assert(elseKeyword);
var elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line;
return elseKeywordStartLine === childStartLine;
}
}
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorOptions): number {
if (node.parent) {
switch (node.parent.kind) {
case SyntaxKind.TypeReference:
if ((<TypeReferenceNode>node.parent).typeArguments) {
return getActualIndentationFromList((<TypeReferenceNode>node.parent).typeArguments);
}
break;
case SyntaxKind.ObjectLiteral:
return getActualIndentationFromList((<ObjectLiteral>node.parent).properties);
case SyntaxKind.TypeLiteral:
return getActualIndentationFromList((<TypeLiteralNode>node.parent).members);
case SyntaxKind.ArrayLiteral:
return getActualIndentationFromList((<ArrayLiteral>node.parent).elements);
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.Method:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature:
if ((<SignatureDeclaration>node.parent).typeParameters && node.end < (<SignatureDeclaration>node.parent).typeParameters.end) {
return getActualIndentationFromList((<SignatureDeclaration>node.parent).typeParameters);
}
return getActualIndentationFromList((<SignatureDeclaration>node.parent).parameters);
case SyntaxKind.NewExpression:
case SyntaxKind.CallExpression:
if ((<CallExpression>node.parent).typeArguments && node.end < (<CallExpression>node.parent).typeArguments.end) {
return getActualIndentationFromList((<CallExpression>node.parent).typeArguments);
}
return getActualIndentationFromList((<CallExpression>node.parent).arguments);
}
}
return -1;
function getActualIndentationFromList(list: Node[]): number {
var index = indexOf(list, node);
return index !== -1 ? deriveActualIndentationFromList(list, index, sourceFile, options) : -1;
}
}
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: EditorOptions): number {
Debug.assert(index >= 0 && index < list.length);
var node = list[index];
// walk toward the start of the list starting from current node and check if the line is the same for all items.
// if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i]
var lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile);
for (var i = index - 1; i >= 0; --i) {
if (list[i].kind === SyntaxKind.CommaToken) {
continue;
}
// skip list items that ends on the same line with the current list element
var prevEndLine = sourceFile.getLineAndCharacterFromPosition(list[i].end).line;
if (prevEndLine !== lineAndCharacter.line) {
return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options);
}
lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile);
}
return -1;
}
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorOptions): number {
var lineStart = sourceFile.getPositionFromLineAndCharacter(lineAndCharacter.line, 1);
var column = 0;
for (var i = 0; i < lineAndCharacter.character; ++i) {
var charCode = sourceFile.text.charCodeAt(lineStart + i);
if (!isWhiteSpace(charCode)) {
return column;
}
if (charCode === CharacterCodes.tab) {
column += options.TabSize;
}
else {
column++;
}
}
return column;
}
function nodeContentIsIndented(parent: Node, child: Node): boolean {
switch (parent.kind) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
return true;
case SyntaxKind.ModuleDeclaration:
// ModuleBlock should take care of indentation
return false;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.Method:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.Constructor:
// FunctionBlock should take care of indentation
return false;
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForStatement:
return child && child.kind !== SyntaxKind.Block;
case SyntaxKind.IfStatement:
return child && child.kind !== SyntaxKind.Block;
case SyntaxKind.TryStatement:
// TryBlock\CatchBlock\FinallyBlock should take care of indentation
return false;
case SyntaxKind.ArrayLiteral:
case SyntaxKind.Block:
case SyntaxKind.FunctionBlock:
case SyntaxKind.TryBlock:
case SyntaxKind.CatchBlock:
case SyntaxKind.FinallyBlock:
case SyntaxKind.ModuleBlock:
case SyntaxKind.ObjectLiteral:
case SyntaxKind.TypeLiteral:
case SyntaxKind.SwitchStatement:
case SyntaxKind.DefaultClause:
case SyntaxKind.CaseClause:
case SyntaxKind.ParenExpression:
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
case SyntaxKind.VariableStatement:
case SyntaxKind.VariableDeclaration:
return true;
default:
return false;
}
}
/*
* Checks if node ends with 'expectedLastToken'.
* If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
*/
function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean {
var children = n.getChildren(sourceFile);
if (children.length) {
var last = children[children.length - 1];
if (last.kind === expectedLastToken) {
return true;
}
else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) {
return children[children.length - 2].kind === expectedLastToken;
}
}
return false;
}
/*
* This function is always called when position of the cursor is located after the node
*/
function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
switch (n.kind) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ObjectLiteral:
case SyntaxKind.Block:
case SyntaxKind.CatchBlock:
case SyntaxKind.FinallyBlock:
case SyntaxKind.FunctionBlock:
case SyntaxKind.ModuleBlock:
case SyntaxKind.SwitchStatement:
return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile);
case SyntaxKind.ParenExpression:
case SyntaxKind.CallSignature:
case SyntaxKind.CallExpression:
case SyntaxKind.ConstructSignature:
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.Method:
case SyntaxKind.ArrowFunction:
return !(<FunctionDeclaration>n).body || isCompletedNode((<FunctionDeclaration>n).body, sourceFile);
case SyntaxKind.ModuleDeclaration:
return (<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile);
case SyntaxKind.IfStatement:
if ((<IfStatement>n).elseStatement) {
return isCompletedNode((<IfStatement>n).elseStatement, sourceFile);
}
return isCompletedNode((<IfStatement>n).thenStatement, sourceFile);
case SyntaxKind.ExpressionStatement:
return isCompletedNode((<ExpressionStatement>n).expression, sourceFile);
case SyntaxKind.ArrayLiteral:
return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile);
case SyntaxKind.Missing:
return false;
case SyntaxKind.CaseClause:
case SyntaxKind.DefaultClause:
// there is no such thing as terminator token for CaseClause\DefaultClause so for simplicitly always consider them non-completed
return false;
case SyntaxKind.WhileStatement:
return isCompletedNode((<WhileStatement>n).statement, sourceFile);
case SyntaxKind.DoStatement:
// rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')';
var hasWhileKeyword = forEach(n.getChildren(), c => c.kind === SyntaxKind.WhileKeyword && c);
if(hasWhileKeyword) {
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
}
return isCompletedNode((<DoStatement>n).statement, sourceFile);
default:
return true;
}
}
}
}

View File

@ -12,8 +12,8 @@
/// <reference path='signatureHelp.ts' />
/// <reference path='utilities.ts' />
/// <reference path='formatting\formatting.ts' />
/// <reference path='formatting\smartIndenter.ts' />
/// <reference path='formatting\format.ts' />
/// <reference path='smartIndenter.ts' />
/// <reference path='format.ts' />
/// <reference path='core\references.ts' />
/// <reference path='resources\references.ts' />

View File

@ -1,4 +1,4 @@
///<reference path='..\services.ts' />
///<reference path='services.ts' />
module ts.formatting {
export module SmartIndenter {

View File

@ -5,6 +5,43 @@ module ts {
list: Node;
}
export function getEndLinePosition(line: number, sourceFile: SourceFile): number {
Debug.assert(line >= 1);
var lineStarts = sourceFile.getLineStarts();
line = line - 1;
if (line === lineStarts.length - 1) {
// last line - return EOF
return sourceFile.text.length - 1;
}
else {
// current line start
var start = lineStarts[line];
// take the start position of the next line -1 = it should be some line break
var pos = lineStarts[line + 1] - 1;
Debug.assert(isLineBreak(sourceFile.text.charCodeAt(pos)));
// walk backwards skipping line breaks, stop the the beginning of current line.
// i.e:
// <some text>
// $ <- end of line for this position should match the start position
while (start <= pos && isLineBreak(sourceFile.text.charCodeAt(pos))) {
pos--;
}
return pos;
}
}
export function getStartPositionOfLine(line: number, sourceFile: SourceFile): number {
Debug.assert(line >= 1);
return sourceFile.getLineStarts()[line - 1];
}
export function getStartLinePositionForPosition(position: number, sourceFile: SourceFile): number {
var lineStarts = sourceFile.getLineStarts();
var line = sourceFile.getLineAndCharacterFromPosition(position).line;
return lineStarts[line - 1];
}
export function rangeContainsRange(r1: TextRange, r2: TextRange): boolean {
return startEndContainsRange(r1.pos, r1.end, r2);
}