@@ -36,24 +36,7 @@ namespace ts.formatting {
3636
3737 const enclosingCommentRange = getRangeOfEnclosingComment ( sourceFile , position , /*onlyMultiLine*/ true , precedingToken || null ) ; // tslint:disable-line:no-null-keyword
3838 if ( enclosingCommentRange ) {
39- const previousLine = getLineAndCharacterOfPosition ( sourceFile , position ) . line - 1 ;
40- const commentStartLine = getLineAndCharacterOfPosition ( sourceFile , enclosingCommentRange . pos ) . line ;
41-
42- Debug . assert ( commentStartLine >= 0 ) ;
43-
44- if ( previousLine <= commentStartLine ) {
45- return findFirstNonWhitespaceColumn ( getStartPositionOfLine ( commentStartLine , sourceFile ) , position , sourceFile , options ) ;
46- }
47-
48- const startPostionOfLine = getStartPositionOfLine ( previousLine , sourceFile ) ;
49- const { column, character } = findFirstNonWhitespaceCharacterAndColumn ( startPostionOfLine , position , sourceFile , options ) ;
50-
51- if ( column === 0 ) {
52- return column ;
53- }
54-
55- const firstNonWhitespaceCharacterCode = sourceFile . text . charCodeAt ( startPostionOfLine + character ) ;
56- return firstNonWhitespaceCharacterCode === CharacterCodes . asterisk ? column - 1 : column ;
39+ return getCommentIndent ( sourceFile , position , options , enclosingCommentRange ) ;
5740 }
5841
5942 if ( ! precedingToken ) {
@@ -72,20 +55,7 @@ namespace ts.formatting {
7255 // for block indentation, we should look for a line which contains something that's not
7356 // whitespace.
7457 if ( options . indentStyle === IndentStyle . Block ) {
75-
76- // move backwards until we find a line with a non-whitespace character,
77- // then find the first non-whitespace character for that line.
78- let current = position ;
79- while ( current > 0 ) {
80- const char = sourceFile . text . charCodeAt ( current ) ;
81- if ( ! isWhiteSpaceLike ( char ) ) {
82- break ;
83- }
84- current -- ;
85- }
86-
87- const lineStart = ts . getLineStartPositionForPosition ( current , sourceFile ) ;
88- return SmartIndenter . findFirstNonWhitespaceColumn ( lineStart , current , sourceFile , options ) ;
58+ return getBlockIndent ( sourceFile , position , options ) ;
8959 }
9060
9161 if ( precedingToken . kind === SyntaxKind . CommaToken && precedingToken . parent . kind !== SyntaxKind . BinaryExpression ) {
@@ -96,26 +66,60 @@ namespace ts.formatting {
9666 }
9767 }
9868
69+ return getSmartIndent ( sourceFile , position , precedingToken , lineAtPosition , assumeNewLineBeforeCloseBrace , options ) ;
70+ }
71+
72+ function getCommentIndent ( sourceFile : SourceFile , position : number , options : EditorSettings , enclosingCommentRange : CommentRange ) : number {
73+ const previousLine = getLineAndCharacterOfPosition ( sourceFile , position ) . line - 1 ;
74+ const commentStartLine = getLineAndCharacterOfPosition ( sourceFile , enclosingCommentRange . pos ) . line ;
75+
76+ Debug . assert ( commentStartLine >= 0 ) ;
77+
78+ if ( previousLine <= commentStartLine ) {
79+ return findFirstNonWhitespaceColumn ( getStartPositionOfLine ( commentStartLine , sourceFile ) , position , sourceFile , options ) ;
80+ }
81+
82+ const startPostionOfLine = getStartPositionOfLine ( previousLine , sourceFile ) ;
83+ const { column, character } = findFirstNonWhitespaceCharacterAndColumn ( startPostionOfLine , position , sourceFile , options ) ;
84+
85+ if ( column === 0 ) {
86+ return column ;
87+ }
88+
89+ const firstNonWhitespaceCharacterCode = sourceFile . text . charCodeAt ( startPostionOfLine + character ) ;
90+ return firstNonWhitespaceCharacterCode === CharacterCodes . asterisk ? column - 1 : column ;
91+ }
92+
93+ function getBlockIndent ( sourceFile : SourceFile , position : number , options : EditorSettings ) : number {
94+ // move backwards until we find a line with a non-whitespace character,
95+ // then find the first non-whitespace character for that line.
96+ let current = position ;
97+ while ( current > 0 ) {
98+ const char = sourceFile . text . charCodeAt ( current ) ;
99+ if ( ! isWhiteSpaceLike ( char ) ) {
100+ break ;
101+ }
102+ current -- ;
103+ }
104+
105+ const lineStart = ts . getLineStartPositionForPosition ( current , sourceFile ) ;
106+ return findFirstNonWhitespaceColumn ( lineStart , current , sourceFile , options ) ;
107+ }
108+
109+ function getSmartIndent ( sourceFile : SourceFile , position : number , precedingToken : Node , lineAtPosition : number , assumeNewLineBeforeCloseBrace : boolean , options : EditorSettings ) : number {
99110 // try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken'
100111 // if such node is found - compute initial indentation for 'position' inside this node
101- let previous : Node ;
112+ let previous : Node | undefined ;
102113 let current = precedingToken ;
103- let currentStart : LineAndCharacter ;
104- let indentationDelta : number ;
105-
106114 while ( current ) {
107- if ( positionBelongsToNode ( current , position , sourceFile ) && shouldIndentChildNode ( current , previous ) ) {
108- currentStart = getStartLineAndCharacterForNode ( current , sourceFile ) ;
109-
115+ if ( positionBelongsToNode ( current , position , sourceFile ) && shouldIndentChildNode ( current , previous , /*isNextChild*/ true ) ) {
116+ const currentStart = getStartLineAndCharacterForNode ( current , sourceFile ) ;
110117 const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor ( precedingToken , current , lineAtPosition , sourceFile ) ;
111- if ( nextTokenKind !== NextTokenKind . Unknown ) {
118+ const indentationDelta = nextTokenKind !== NextTokenKind . Unknown
112119 // handle cases when codefix is about to be inserted before the close brace
113- indentationDelta = assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind . CloseBrace ? options . indentSize : 0 ;
114- }
115- else {
116- indentationDelta = lineAtPosition !== currentStart . line ? options . indentSize : 0 ;
117- }
118- break ;
120+ ? assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind . CloseBrace ? options . indentSize : 0
121+ : lineAtPosition !== currentStart . line ? options . indentSize : 0 ;
122+ return getIndentationForNodeWorker ( current , currentStart , /*ignoreActualIndentationRange*/ undefined , indentationDelta , sourceFile , /*isNextChild*/ true , options ) ;
119123 }
120124
121125 // check if current node is a list item - if yes, take indentation from it
@@ -131,18 +135,13 @@ namespace ts.formatting {
131135 previous = current ;
132136 current = current . parent ;
133137 }
134-
135- if ( ! current ) {
136- // no parent was found - return the base indentation of the SourceFile
137- return getBaseIndentation ( options ) ;
138- }
139-
140- return getIndentationForNodeWorker ( current , currentStart , /*ignoreActualIndentationRange*/ undefined , indentationDelta , sourceFile , options ) ;
138+ // no parent was found - return the base indentation of the SourceFile
139+ return getBaseIndentation ( options ) ;
141140 }
142141
143142 export function getIndentationForNode ( n : Node , ignoreActualIndentationRange : TextRange , sourceFile : SourceFile , options : EditorSettings ) : number {
144143 const start = sourceFile . getLineAndCharacterOfPosition ( n . getStart ( sourceFile ) ) ;
145- return getIndentationForNodeWorker ( n , start , ignoreActualIndentationRange , /*indentationDelta*/ 0 , sourceFile , options ) ;
144+ return getIndentationForNodeWorker ( n , start , ignoreActualIndentationRange , /*indentationDelta*/ 0 , sourceFile , /*isNextChild*/ false , options ) ;
146145 }
147146
148147 export function getBaseIndentation ( options : EditorSettings ) {
@@ -155,11 +154,9 @@ namespace ts.formatting {
155154 ignoreActualIndentationRange : TextRange ,
156155 indentationDelta : number ,
157156 sourceFile : SourceFile ,
157+ isNextChild : boolean ,
158158 options : EditorSettings ) : number {
159-
160- let parent : Node = current . parent ;
161- let containingListOrParentStart : LineAndCharacter ;
162-
159+ let parent = current . parent ! ;
163160 // Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if
164161 // * parent and child nodes start on the same line, or
165162 // * parent is an IfStatement and child starts on the same line as an 'else clause'.
@@ -178,7 +175,7 @@ namespace ts.formatting {
178175 }
179176 }
180177
181- containingListOrParentStart = getContainingListOrParentStart ( parent , current , sourceFile ) ;
178+ const containingListOrParentStart = getContainingListOrParentStart ( parent , current , sourceFile ) ;
182179 const parentAndChildShareLine =
183180 containingListOrParentStart . line === currentStart . line ||
184181 childStartsOnTheSameLineWithElseInIfStatement ( parent , current , currentStart . line , sourceFile ) ;
@@ -196,7 +193,7 @@ namespace ts.formatting {
196193 }
197194
198195 // increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
199- if ( shouldIndentChildNode ( parent , current ) && ! parentAndChildShareLine ) {
196+ if ( shouldIndentChildNode ( parent , current , isNextChild ) && ! parentAndChildShareLine ) {
200197 indentationDelta += options . indentSize ;
201198 }
202199
@@ -214,7 +211,7 @@ namespace ts.formatting {
214211
215212 current = parent ;
216213 parent = current . parent ;
217- currentStart = useTrueStart ? sourceFile . getLineAndCharacterOfPosition ( current . getStart ( ) ) : containingListOrParentStart ;
214+ currentStart = useTrueStart ? sourceFile . getLineAndCharacterOfPosition ( current . getStart ( sourceFile ) ) : containingListOrParentStart ;
218215 }
219216
220217 return indentationDelta + getBaseIndentation ( options ) ;
@@ -533,8 +530,7 @@ namespace ts.formatting {
533530 return false ;
534531 }
535532
536- /* @internal */
537- export function nodeWillIndentChild ( parent : TextRangeWithKind , child : TextRangeWithKind , indentByDefault : boolean ) {
533+ export function nodeWillIndentChild ( parent : TextRangeWithKind , child : TextRangeWithKind | undefined , indentByDefault : boolean ) : boolean {
538534 const childKind = child ? child . kind : SyntaxKind . Unknown ;
539535 switch ( parent . kind ) {
540536 case SyntaxKind . DoStatement :
@@ -555,19 +551,52 @@ namespace ts.formatting {
555551 return childKind !== SyntaxKind . NamedExports ;
556552 case SyntaxKind . ImportDeclaration :
557553 return childKind !== SyntaxKind . ImportClause ||
558- ( ( < ImportClause > child ) . namedBindings && ( < ImportClause > child ) . namedBindings . kind !== SyntaxKind . NamedImports ) ;
554+ ( ! ! ( < ImportClause > child ) . namedBindings && ( < ImportClause > child ) . namedBindings . kind !== SyntaxKind . NamedImports ) ;
559555 case SyntaxKind . JsxElement :
560556 return childKind !== SyntaxKind . JsxClosingElement ;
561557 }
562558 // No explicit rule for given nodes so the result will follow the default value argument
563559 return indentByDefault ;
564560 }
565561
566- /*
567- Function returns true when the parent node should indent the given child by an explicit rule
568- */
569- export function shouldIndentChildNode ( parent : TextRangeWithKind , child ?: TextRangeWithKind ) : boolean {
570- return nodeContentIsAlwaysIndented ( parent . kind ) || nodeWillIndentChild ( parent , child , /*indentByDefault*/ false ) ;
562+ function isControlFlowEndingStatement ( kind : SyntaxKind , parent : TextRangeWithKind ) : boolean {
563+ switch ( kind ) {
564+ case SyntaxKind . ReturnStatement :
565+ case SyntaxKind . ThrowStatement :
566+ switch ( parent . kind ) {
567+ case SyntaxKind . Block :
568+ const grandParent = ( parent as Node ) . parent ;
569+ switch ( grandParent && grandParent . kind ) {
570+ case SyntaxKind . FunctionDeclaration :
571+ case SyntaxKind . FunctionExpression :
572+ // We may want to write inner functions after this.
573+ return false ;
574+ default :
575+ return true ;
576+ }
577+ case SyntaxKind . CaseClause :
578+ case SyntaxKind . DefaultClause :
579+ case SyntaxKind . SourceFile :
580+ case SyntaxKind . ModuleBlock :
581+ return true ;
582+ default :
583+ throw Debug . fail ( ) ;
584+ }
585+ case SyntaxKind . ContinueStatement :
586+ case SyntaxKind . BreakStatement :
587+ return true ;
588+ default :
589+ return false ;
590+ }
591+ }
592+
593+ /**
594+ * True when the parent node should indent the given child by an explicit rule.
595+ * @param isNextChild If true, we are judging indent of a hypothetical child *after* this one, not the current child.
596+ */
597+ export function shouldIndentChildNode ( parent : TextRangeWithKind , child ?: TextRangeWithKind , isNextChild = false ) : boolean {
598+ return ( nodeContentIsAlwaysIndented ( parent . kind ) || nodeWillIndentChild ( parent , child , /*indentByDefault*/ false ) )
599+ && ! ( isNextChild && child && isControlFlowEndingStatement ( child . kind , parent ) ) ;
571600 }
572601 }
573602}
0 commit comments