Merge pull request #671 from Microsoft/new_smart_indentation

LS: smart indentation
This commit is contained in:
Vladimir Matveev 2014-09-16 11:33:37 -07:00
commit fdc9b9d285
21 changed files with 698 additions and 108 deletions

View file

@ -318,7 +318,7 @@ function exec(cmd, completeHandler) {
complete();
})
try{
ex.run();
ex.run();
} catch(e) {
console.log('Exception: ' + e)
}
@ -385,7 +385,7 @@ desc("Generates code coverage data via instanbul")
task("generate-code-coverage", ["tests", builtLocalDirectory], function () {
var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run;
console.log(cmd);
exec(cmd);
exec(cmd);
}, { async: true });
// Browser tests

View file

@ -50,8 +50,8 @@ module ts {
return node.pos;
}
export function getTokenPosOfNode(node: Node): number {
return skipTrivia(getSourceFileOfNode(node).text, node.pos);
export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile): number {
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
}
export function getSourceTextOfNodeFromSourceText(sourceText: string, node: Node): string {
@ -487,6 +487,32 @@ module ts {
return false;
}
export function isStatement(n: Node): boolean {
switch(n.kind) {
case SyntaxKind.BreakStatement:
case SyntaxKind.ContinueStatement:
case SyntaxKind.DebuggerStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.EmptyStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.LabeledStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.ThrowKeyword:
case SyntaxKind.TryStatement:
case SyntaxKind.VariableStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.ExportAssignment:
return true;
default:
return false;
}
}
// True if the given identifier, string literal, or number literal is the name of a declaration node
export function isDeclarationOrFunctionExpressionOrCatchVariableName(name: Node): boolean {
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {

View file

@ -223,6 +223,8 @@ module ts {
LastTypeNode = TupleType,
FirstPunctuation = OpenBraceToken,
LastPunctuation = CaretEqualsToken,
FirstToken = EndOfFileToken,
LastToken = StringKeyword
}
export enum NodeFlags {
@ -988,6 +990,15 @@ module ts {
AMD,
}
export interface LineAndCharacter {
line: number;
/*
* This value denotes the character position in line and is different from the 'column' because of tab characters.
*/
character: number;
}
export enum ScriptTarget {
ES3,
ES5,

View file

@ -36,5 +36,4 @@
///<reference path='indentationNodeContextPool.ts' />
///<reference path='indentationTrackingWalker.ts' />
///<reference path='multipleTokenIndenter.ts' />
///<reference path='singleTokenIndenter.ts' />
///<reference path='formatter.ts' />

View file

@ -1,46 +0,0 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///<reference path='formatting.ts' />
module TypeScript.Services.Formatting {
export class SingleTokenIndenter extends IndentationTrackingWalker {
private indentationAmount: number = null;
private indentationPosition: number;
constructor(indentationPosition: number, sourceUnit: SourceUnitSyntax, snapshot: ITextSnapshot, indentFirstToken: boolean, options: FormattingOptions) {
super(new TextSpan(indentationPosition, 1), sourceUnit, snapshot, indentFirstToken, options);
this.indentationPosition = indentationPosition;
}
public static getIndentationAmount(position: number, sourceUnit: SourceUnitSyntax, snapshot: ITextSnapshot, options: FormattingOptions): number {
var walker = new SingleTokenIndenter(position, sourceUnit, snapshot, true, options);
visitNodeOrToken(walker, sourceUnit);
return walker.indentationAmount;
}
public indentToken(token: ISyntaxToken, indentationAmount: number, commentIndentationAmount: number): void {
// Compute an indentation string for this token
if (token.fullWidth() === 0 || (this.indentationPosition - this.position() < token.leadingTriviaWidth())) {
// The position is in the leading trivia, use comment indentation
this.indentationAmount = commentIndentationAmount;
}
else {
this.indentationAmount = indentationAmount;
}
}
}
}

View file

@ -0,0 +1,522 @@
///<reference path='..\services.ts' />
module ts.formatting {
export module SmartIndenter {
export function getIndentation(position: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
if (position > sourceFile.text.length) {
return 0; // past EOF
}
var precedingToken = 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.indentSpaces : 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;
}
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.indentSpaces;
}
current = parent;
currentStart = parentStart;
parent = current.parent;
}
return indentationDelta;
}
/*
* Function returns -1 if indentation cannot be determined
*/
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
var itemInfo = findPrecedingListItem(commaToken);
return deriveActualIndentationFromList(itemInfo.list.getChildren(), itemInfo.listItemIndex, 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: TypeScript.FormattingOptions): 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 = 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 findPrecedingListItem(commaToken: Node): { listItemIndex: number; list: Node } {
// CommaToken node is synthetic and thus will be stored in SyntaxList, however parent of the CommaToken points to the container of the SyntaxList skipping the list.
// In order to find the preceding list item we first need to locate SyntaxList itself and then search for the position of CommaToken
var syntaxList = forEach(commaToken.parent.getChildren(), c => {
// find syntax list that covers the span of CommaToken
if (c.kind == SyntaxKind.SyntaxList && c.pos <= commaToken.end && c.end >= commaToken.end) {
return c;
}
});
Debug.assert(syntaxList);
var children = syntaxList.getChildren();
var commaIndex = indexOf(children, commaToken);
Debug.assert(commaIndex !== -1 && commaIndex !== 0);
return {
listItemIndex: commaIndex - 1,
list: syntaxList
};
}
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: TypeScript.FormattingOptions): 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: TypeScript.FormattingOptions): 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: TypeScript.FormattingOptions): 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.spacesPerTab;
}
else {
column++;
}
}
return column;
}
function findNextToken(previousToken: Node, parent: Node): Node {
return find(parent);
function find(n: Node): Node {
if (isToken(n) && n.pos === previousToken.end) {
// this is token that starts at the end of previous token - return it
return n;
}
var children = n.getChildren();
for (var i = 0, len = children.length; i < len; ++i) {
var child = children[i];
var shouldDiveInChildNode =
// previous token is enclosed somewhere in the child
(child.pos <= previousToken.pos && child.end > previousToken.end) ||
// previous token ends exactly at the beginning of child
(child.pos === previousToken.end);
if (shouldDiveInChildNode && nodeHasTokens(child)) {
return find(child);
}
}
return undefined;
}
}
function findPrecedingToken(position: number, sourceFile: SourceFile): Node {
return find(sourceFile);
function findRightmostToken(n: Node): Node {
if (isToken(n)) {
return n;
}
var children = n.getChildren();
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
return candidate && findRightmostToken(candidate);
}
function find(n: Node): Node {
if (isToken(n)) {
return n;
}
var children = n.getChildren();
for (var i = 0, len = children.length; i < len; ++i) {
var child = children[i];
if (nodeHasTokens(child)) {
if (position < child.end) {
if (child.getStart(sourceFile) >= position) {
// actual start of the node is past the position - previous token should be at the end of previous child
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i);
return candidate && findRightmostToken(candidate)
}
else {
// candidate should be in this node
return find(child);
}
}
}
}
Debug.assert(n.kind === SyntaxKind.SourceFile);
// Here we know that none of child token nodes embrace the position,
// the only known case is when position is at the end of the file.
// Try to find the rightmost token in the file without filtering.
// Namely we are skipping the check: 'position < node.end'
if (children.length) {
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
return candidate && findRightmostToken(candidate);
}
}
/// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition'
function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node {
for (var i = exclusiveStartPosition - 1; i >= 0; --i) {
if (nodeHasTokens(children[i])) {
return children[i];
}
}
}
}
/*
* Checks if node is something that can contain tokens (except EOF) - filters out EOF tokens, Missing\Omitted expressions, empty SyntaxLists and expression statements that wrap any of listed nodes.
*/
function nodeHasTokens(n: Node): boolean {
if (n.kind === SyntaxKind.ExpressionStatement) {
return nodeHasTokens((<ExpressionStatement>n).expression);
}
if (n.kind === SyntaxKind.EndOfFileToken || n.kind === SyntaxKind.OmittedExpression || n.kind === SyntaxKind.Missing) {
return false;
}
// SyntaxList is already realized so getChildCount should be fast and non-expensive
return n.kind !== SyntaxKind.SyntaxList || n.getChildCount() !== 0;
}
function isToken(n: Node): boolean {
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
}
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

@ -11,6 +11,7 @@
/// <reference path='breakpoints.ts' />
/// <reference path='indentation.ts' />
/// <reference path='formatting\formatting.ts' />
/// <reference path='formatting\smartIndenter.ts' />
/// <reference path='core\references.ts' />
/// <reference path='resources\references.ts' />
@ -27,18 +28,18 @@
module ts {
export interface Node {
getSourceFile(): SourceFile;
getChildCount(): number;
getChildAt(index: number): Node;
getChildren(): Node[];
getStart(): number;
getChildCount(sourceFile?: SourceFile): number;
getChildAt(index: number, sourceFile?: SourceFile): Node;
getChildren(sourceFile?: SourceFile): Node[];
getStart(sourceFile?: SourceFile): number;
getFullStart(): number;
getEnd(): number;
getWidth(): number;
getWidth(sourceFile?: SourceFile): number;
getFullWidth(): number;
getLeadingTriviaWidth(): number;
getFullText(): string;
getFirstToken(): Node;
getLastToken(): Node;
getLeadingTriviaWidth(sourceFile?: SourceFile): number;
getFullText(sourceFile?: SourceFile): string;
getFirstToken(sourceFile?: SourceFile): Node;
getLastToken(sourceFile?: SourceFile): Node;
}
export interface Symbol {
@ -100,8 +101,8 @@ module ts {
return <SourceFile>node;
}
public getStart(): number {
return getTokenPosOfNode(this);
public getStart(sourceFile?: SourceFile): number {
return getTokenPosOfNode(this, sourceFile);
}
public getFullStart(): number {
@ -112,20 +113,20 @@ module ts {
return this.end;
}
public getWidth(): number {
return this.getEnd() - this.getStart();
public getWidth(sourceFile?: SourceFile): number {
return this.getEnd() - this.getStart(sourceFile);
}
public getFullWidth(): number {
return this.end - this.getFullStart();
}
public getLeadingTriviaWidth(): number {
return this.getStart() - this.pos;
public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
return this.getStart(sourceFile) - this.pos;
}
public getFullText(): string {
return this.getSourceFile().text.substring(this.pos, this.end);
public getFullText(sourceFile?: SourceFile): string {
return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
}
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
@ -157,9 +158,9 @@ module ts {
return list;
}
private createChildren() {
private createChildren(sourceFile?: SourceFile) {
if (this.kind > SyntaxKind.Missing) {
scanner.setText(this.getSourceFile().text);
scanner.setText((sourceFile || this.getSourceFile()).text);
var children: Node[] = [];
var pos = this.pos;
var processNode = (node: Node) => {
@ -185,36 +186,36 @@ module ts {
this._children = children || emptyArray;
}
public getChildCount(): number {
if (!this._children) this.createChildren();
public getChildCount(sourceFile?: SourceFile): number {
if (!this._children) this.createChildren(sourceFile);
return this._children.length;
}
public getChildAt(index: number): Node {
if (!this._children) this.createChildren();
public getChildAt(index: number, sourceFile?: SourceFile): Node {
if (!this._children) this.createChildren(sourceFile);
return this._children[index];
}
public getChildren(): Node[] {
if (!this._children) this.createChildren();
public getChildren(sourceFile?: SourceFile): Node[] {
if (!this._children) this.createChildren(sourceFile);
return this._children;
}
public getFirstToken(): Node {
var children = this.getChildren();
public getFirstToken(sourceFile?: SourceFile): Node {
var children = this.getChildren(sourceFile);
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.kind < SyntaxKind.Missing) return child;
if (child.kind > SyntaxKind.Missing) return child.getFirstToken();
if (child.kind > SyntaxKind.Missing) return child.getFirstToken(sourceFile);
}
}
public getLastToken(): Node {
var children = this.getChildren();
public getLastToken(sourceFile?: SourceFile): Node {
var children = this.getChildren(sourceFile);
for (var i = children.length - 1; i >= 0; i--) {
var child = children[i];
if (child.kind < SyntaxKind.Missing) return child;
if (child.kind > SyntaxKind.Missing) return child.getLastToken();
if (child.kind > SyntaxKind.Missing) return child.getLastToken(sourceFile);
}
}
}
@ -3450,15 +3451,11 @@ module ts {
function getIndentationAtPosition(filename: string, position: number, editorOptions: EditorOptions) {
filename = TypeScript.switchToForwardSlashes(filename);
var syntaxTree = getSyntaxTree(filename);
var scriptSnapshot = syntaxTreeCache.getCurrentScriptSnapshot(filename);
var scriptText = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
var textSnapshot = new TypeScript.Services.Formatting.TextSnapshot(scriptText);
var sourceFile = getCurrentSourceFile(filename);
var options = new TypeScript.FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
return TypeScript.Services.Formatting.SingleTokenIndenter.getIndentationAmount(position, syntaxTree.sourceUnit(), textSnapshot, options);
return formatting.SmartIndenter.getIndentation(position, sourceFile, options);
}
function getFormattingManager(filename: string, options: FormatCodeOptions) {

View file

@ -2,10 +2,10 @@
//// declare var $: any;
//// $(".contentDiv").each(function (index, element) {/**/
//// // <-- ensure cursor is here after return on above
//// }); // Ensure indent is 0 after LBrace
//// });
goTo.marker();
edit.insert("\n");
verify.indentationIs(4);
edit.insert("}");
verify.indentationIs(0);
verify.indentationIs(4); // keep arguments indented

View file

@ -154,7 +154,7 @@
////
////function argumentsListIndentation(bar,
//// blah,
////{| "indent": 0 |}
//// {| "indent": 13 |}
////);
////
////

View file

@ -8,5 +8,4 @@
goTo.marker("1");
edit.insert("\r\n");
// Won't-fixed: Disable SmartIndent inside multiline string in TS. Should be 0
verify.indentationIs(8);
verify.indentationIs(0);

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
//// class A {
//// /*1*/
//// }
////module M {
//// class C {
//// /*2*/
//// }
////}
goTo.marker("1");
verify.indentationIs(12);
goTo.marker("2");
verify.indentationIs(16);

View file

@ -1,9 +1,10 @@
/// <reference path='fourslash.ts'/>
////function foo(bar,
//// blah,
//// blah, baz,
//// /**/
////) { };
goTo.marker();
verify.indentationIs(0);
// keep indentation of 'blah'
verify.indentationIs(13);

View file

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts'/>
////class A {
//// {| "indentation": 0|} }
////class B {
//// var x = 1;
//// {| "indentation": 0|} }
test.markers().forEach((marker) => {
verify.indentationAtPositionIs(marker.fileName, marker.position, marker.data.indentation);
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
//// do /*1*/ {
//// } while (true)
////
//// do { /*2*/
//// } /*3*/while (true)/*4*/
function verifyIndentationAfterNewLine(marker: string, indentation: number): void {
goTo.marker(marker);
edit.insert("\r\n");
verify.indentationIs(indentation);
}
verifyIndentationAfterNewLine("1", 0);
verifyIndentationAfterNewLine("2", 4);
verifyIndentationAfterNewLine("3", 0);
verifyIndentationAfterNewLine("4", 0);

View file

@ -0,0 +1,20 @@
/// <reference path='fourslash.ts'/>
//// if /*1*/(true) { }
////
//// if (true) /*2*/ { /*3*/
//// } /*4*/
////
//// if (1 === /*5*/ 2) { }
function verifyIndentationAfterNewLine(marker: string, indentation: number): void {
goTo.marker(marker);
edit.insert("\r\n");
verify.indentationIs(indentation);
}
verifyIndentationAfterNewLine("1", 4);
verifyIndentationAfterNewLine("2", 0);
verifyIndentationAfterNewLine("3", 4);
verifyIndentationAfterNewLine("4", 0);
verifyIndentationAfterNewLine("5", 4);

View file

@ -21,15 +21,11 @@
//// }
////}
// Behavior below is not ideal, ideal is in comments
goTo.marker("1");
//verify.indentationIs(0);
verify.indentationIs(8);
verify.indentationIs(0);
goTo.marker("2");
//verify.indentationIs(0);
verify.indentationIs(4);
verify.indentationIs(0);
goTo.marker("3");
//verify.indentationIs(0);
verify.indentationIs(12);
verify.indentationIs(0);

View file

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts'/>
////[1,
//// 2
//// + 3, 4,
//// /*1*/
////[1,
//// 2
//// + 3, 4
//// /*2*/
goTo.marker("1");
verify.indentationIs(4)
goTo.marker("2");
verify.indentationIs(4)

View file

@ -22,7 +22,7 @@ goTo.marker("4");
verify.currentLineContentIs(" c");
goTo.marker("1");
edit.insert("\r\n");
verify.indentationIs(0);
verify.indentationIs(4);
goTo.marker("5");
verify.currentLineContentIs(" //comment");
goTo.marker("6");

View file

@ -11,7 +11,7 @@
//// case 1:
//// {| "indentation": 12 |}
//// break;
//// {| "indentation": 8 |}
//// {| "indentation": 12 |} // content of case clauses is always indented relatively to case clause
//// }
//// {| "indentation": 4 |}
////}

View file

@ -0,0 +1,8 @@
/// <reference path='fourslash.ts'/>
////interface T1 extends T<number,
//// string
//// /**/>
goTo.marker();
verify.indentationIs(32);

View file

@ -8,6 +8,4 @@
goTo.marker();
edit.insert('case 1:\n');
// ideally would be 8
//verify.indentationIs(8);
verify.indentationIs(4);
verify.indentationIs(8);