diff --git a/Jakefile b/Jakefile
index 79907e8a63..1307601fb8 100644
--- a/Jakefile
+++ b/Jakefile
@@ -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
diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index db8fcace15..40d9722af0 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -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) {
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 529903f303..6955689797 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -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,
diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts
index c334e1864a..7570ef936c 100644
--- a/src/services/formatting/formatting.ts
+++ b/src/services/formatting/formatting.ts
@@ -36,5 +36,4 @@
///
///
///
-///
///
\ No newline at end of file
diff --git a/src/services/formatting/singleTokenIndenter.ts b/src/services/formatting/singleTokenIndenter.ts
deleted file mode 100644
index 896c2e8ac0..0000000000
--- a/src/services/formatting/singleTokenIndenter.ts
+++ /dev/null
@@ -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.
-//
-
-///
-
-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;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts
new file mode 100644
index 0000000000..ad07f884cc
--- /dev/null
+++ b/src/services/formatting/smartIndenter.ts
@@ -0,0 +1,522 @@
+///
+
+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 && (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 ((node.parent).typeArguments) {
+ return getActualIndentationFromList((node.parent).typeArguments);
+ }
+ break;
+ case SyntaxKind.ObjectLiteral:
+ return getActualIndentationFromList((node.parent).properties);
+ case SyntaxKind.TypeLiteral:
+ return getActualIndentationFromList((node.parent).members);
+ case SyntaxKind.ArrayLiteral:
+ return getActualIndentationFromList((node.parent).elements);
+ case SyntaxKind.FunctionDeclaration:
+ case SyntaxKind.FunctionExpression:
+ case SyntaxKind.ArrowFunction:
+ case SyntaxKind.Method:
+ case SyntaxKind.CallSignature:
+ case SyntaxKind.ConstructSignature:
+ if ((node.parent).typeParameters && node.end < (node.parent).typeParameters.end) {
+ return getActualIndentationFromList((node.parent).typeParameters);
+ }
+
+ return getActualIndentationFromList((node.parent).parameters);
+ case SyntaxKind.NewExpression:
+ case SyntaxKind.CallExpression:
+ if ((node.parent).typeArguments && node.end < (node.parent).typeArguments.end) {
+ return getActualIndentationFromList((node.parent).typeArguments);
+ }
+
+ return getActualIndentationFromList((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((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 !(n).body || isCompletedNode((n).body, sourceFile);
+ case SyntaxKind.ModuleDeclaration:
+ return (n).body && isCompletedNode((n).body, sourceFile);
+ case SyntaxKind.IfStatement:
+ if ((n).elseStatement) {
+ return isCompletedNode((n).elseStatement, sourceFile);
+ }
+ return isCompletedNode((n).thenStatement, sourceFile);
+ case SyntaxKind.ExpressionStatement:
+ return isCompletedNode((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((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((n).statement, sourceFile);
+ default:
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/services.ts b/src/services/services.ts
index 94f3cefdd5..7447e56df8 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -11,6 +11,7 @@
///
///
///
+///
///
///
@@ -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 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) {
diff --git a/tests/cases/fourslash/chainedFunctionFunctionArgIndent.ts b/tests/cases/fourslash/chainedFunctionFunctionArgIndent.ts
index 8b9a54de52..ac5b74e83c 100644
--- a/tests/cases/fourslash/chainedFunctionFunctionArgIndent.ts
+++ b/tests/cases/fourslash/chainedFunctionFunctionArgIndent.ts
@@ -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
diff --git a/tests/cases/fourslash/indentation.ts b/tests/cases/fourslash/indentation.ts
index 3ff6dac45a..32319ae282 100644
--- a/tests/cases/fourslash/indentation.ts
+++ b/tests/cases/fourslash/indentation.ts
@@ -154,7 +154,7 @@
////
////function argumentsListIndentation(bar,
//// blah,
-////{| "indent": 0 |}
+//// {| "indent": 13 |}
////);
////
////
diff --git a/tests/cases/fourslash/noSmartIndentInsideMultilineString.ts b/tests/cases/fourslash/noSmartIndentInsideMultilineString.ts
index 9018a54b33..692dfe7075 100644
--- a/tests/cases/fourslash/noSmartIndentInsideMultilineString.ts
+++ b/tests/cases/fourslash/noSmartIndentInsideMultilineString.ts
@@ -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);
\ No newline at end of file
+verify.indentationIs(0);
\ No newline at end of file
diff --git a/tests/cases/fourslash/smartIndentActualIndentation.ts b/tests/cases/fourslash/smartIndentActualIndentation.ts
new file mode 100644
index 0000000000..b2874d3c1d
--- /dev/null
+++ b/tests/cases/fourslash/smartIndentActualIndentation.ts
@@ -0,0 +1,17 @@
+///
+
+//// class A {
+//// /*1*/
+//// }
+
+////module M {
+//// class C {
+//// /*2*/
+//// }
+////}
+
+goTo.marker("1");
+verify.indentationIs(12);
+
+goTo.marker("2");
+verify.indentationIs(16);
diff --git a/tests/cases/fourslash/smartIndentAfterAlignedFunctionArgument.ts b/tests/cases/fourslash/smartIndentAfterAlignedFunctionArgument.ts
index 8efd5a9c9b..d5aa2bc431 100644
--- a/tests/cases/fourslash/smartIndentAfterAlignedFunctionArgument.ts
+++ b/tests/cases/fourslash/smartIndentAfterAlignedFunctionArgument.ts
@@ -1,9 +1,10 @@
///
////function foo(bar,
-//// blah,
+//// blah, baz,
//// /**/
////) { };
goTo.marker();
-verify.indentationIs(0);
+// keep indentation of 'blah'
+verify.indentationIs(13);
diff --git a/tests/cases/fourslash/smartIndentCloseBrace.ts b/tests/cases/fourslash/smartIndentCloseBrace.ts
new file mode 100644
index 0000000000..da2094d76c
--- /dev/null
+++ b/tests/cases/fourslash/smartIndentCloseBrace.ts
@@ -0,0 +1,11 @@
+///
+
+////class A {
+//// {| "indentation": 0|} }
+////class B {
+//// var x = 1;
+//// {| "indentation": 0|} }
+
+test.markers().forEach((marker) => {
+ verify.indentationAtPositionIs(marker.fileName, marker.position, marker.data.indentation);
+});
diff --git a/tests/cases/fourslash/smartIndentDoStatement.ts b/tests/cases/fourslash/smartIndentDoStatement.ts
new file mode 100644
index 0000000000..2dff10f927
--- /dev/null
+++ b/tests/cases/fourslash/smartIndentDoStatement.ts
@@ -0,0 +1,17 @@
+///
+//// 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);
diff --git a/tests/cases/fourslash/smartIndentIfStatement.ts b/tests/cases/fourslash/smartIndentIfStatement.ts
new file mode 100644
index 0000000000..fb377ec9c9
--- /dev/null
+++ b/tests/cases/fourslash/smartIndentIfStatement.ts
@@ -0,0 +1,20 @@
+///
+
+//// 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);
\ No newline at end of file
diff --git a/tests/cases/fourslash/smartIndentInsideMultilineString.ts b/tests/cases/fourslash/smartIndentInsideMultilineString.ts
index 8ad4430455..24a20844a9 100644
--- a/tests/cases/fourslash/smartIndentInsideMultilineString.ts
+++ b/tests/cases/fourslash/smartIndentInsideMultilineString.ts
@@ -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);
\ No newline at end of file
+verify.indentationIs(0);
\ No newline at end of file
diff --git a/tests/cases/fourslash/smartIndentListItem.ts b/tests/cases/fourslash/smartIndentListItem.ts
new file mode 100644
index 0000000000..f71db9c865
--- /dev/null
+++ b/tests/cases/fourslash/smartIndentListItem.ts
@@ -0,0 +1,14 @@
+///
+////[1,
+//// 2
+//// + 3, 4,
+//// /*1*/
+////[1,
+//// 2
+//// + 3, 4
+//// /*2*/
+
+goTo.marker("1");
+verify.indentationIs(4)
+goTo.marker("2");
+verify.indentationIs(4)
\ No newline at end of file
diff --git a/tests/cases/fourslash/smartIndentOnFunctionParameters.ts b/tests/cases/fourslash/smartIndentOnFunctionParameters.ts
index c357ab4805..012c4d93b4 100644
--- a/tests/cases/fourslash/smartIndentOnFunctionParameters.ts
+++ b/tests/cases/fourslash/smartIndentOnFunctionParameters.ts
@@ -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");
diff --git a/tests/cases/fourslash/smartIndentStatementSwitch.ts b/tests/cases/fourslash/smartIndentStatementSwitch.ts
index 902abe4e3e..e8d2a02948 100644
--- a/tests/cases/fourslash/smartIndentStatementSwitch.ts
+++ b/tests/cases/fourslash/smartIndentStatementSwitch.ts
@@ -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 |}
////}
diff --git a/tests/cases/fourslash/smartIndentTypeArgumentList.ts b/tests/cases/fourslash/smartIndentTypeArgumentList.ts
new file mode 100644
index 0000000000..24136596ed
--- /dev/null
+++ b/tests/cases/fourslash/smartIndentTypeArgumentList.ts
@@ -0,0 +1,8 @@
+///
+
+////interface T1 extends T
+
+goTo.marker();
+verify.indentationIs(32);
\ No newline at end of file
diff --git a/tests/cases/fourslash/switchIndenting.ts b/tests/cases/fourslash/switchIndenting.ts
index 3fee7ba2e4..8ea6f26408 100644
--- a/tests/cases/fourslash/switchIndenting.ts
+++ b/tests/cases/fourslash/switchIndenting.ts
@@ -8,6 +8,4 @@
goTo.marker();
edit.insert('case 1:\n');
-// ideally would be 8
-//verify.indentationIs(8);
-verify.indentationIs(4);
+verify.indentationIs(8);
\ No newline at end of file