2
2
3
3
module ts . formatting {
4
4
export module SmartIndenter {
5
+
6
+ interface LineAndCharacter {
7
+ line : number ;
8
+ character : number ;
9
+ }
10
+
5
11
export function getIndentation ( position : number , sourceFile : SourceFile , options : TypeScript . FormattingOptions ) : number {
6
12
if ( position > sourceFile . text . length ) {
7
13
return 0 ; // past EOF
@@ -22,56 +28,38 @@ module ts.formatting {
22
28
var lineAtPosition = sourceFile . getLineAndCharacterFromPosition ( position ) . line ;
23
29
24
30
if ( precedingToken . kind === SyntaxKind . CommaToken && precedingToken . parent . kind !== SyntaxKind . BinaryExpression ) {
25
-
26
31
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
27
- var precedingListItem = findPrecedingListItem ( precedingToken ) ;
28
- var precedingListItemStartLineAndChar = sourceFile . getLineAndCharacterFromPosition ( precedingListItem . getStart ( sourceFile ) ) ;
29
- var listStartLine = getStartLineForNode ( precedingListItem . parent , sourceFile ) ;
30
-
31
- if ( precedingListItemStartLineAndChar . line !== listStartLine ) {
32
- return findFirstNonWhitespaceCharacterInLine ( precedingListItemStartLineAndChar . line , precedingListItemStartLineAndChar . character , sourceFile ) ;
33
- // previous list item starts on the different line with list, find first non-whitespace character in this line and use its position as indentation
34
- var lineStartPosition = sourceFile . getPositionFromLineAndCharacter ( precedingListItemStartLineAndChar . line , 1 ) ;
35
- for ( var i = 0 ; i < precedingListItemStartLineAndChar . character ; ++ i ) {
36
- if ( ! isWhiteSpace ( sourceFile . text . charCodeAt ( lineStartPosition + i ) ) ) {
37
- return i ;
38
- }
39
- }
40
-
41
- // seems that this is the first non-whitespace character on the line - return it
42
- return precedingListItemStartLineAndChar . character ;
32
+ var actualIndentation = getActualIndentationForListItemBeforeComma ( precedingToken , sourceFile , options ) ;
33
+ if ( actualIndentation !== - 1 ) {
34
+ return actualIndentation ;
43
35
}
44
36
}
45
37
46
38
// try to find the node that will include 'position' starting from 'precedingToken'
47
39
// if such node is found - compute initial indentation for 'position' inside this node
48
40
var previous : Node ;
49
41
var current = precedingToken ;
50
- var currentStartLine : number ;
42
+ var currentStart : LineAndCharacter ;
51
43
var indentation : number ;
52
44
53
45
while ( current ) {
54
46
if ( isPositionBelongToNode ( current , position , sourceFile ) ) {
55
- currentStartLine = getStartLineForNode ( current , sourceFile ) ;
47
+ currentStart = getStartLineAndCharacterForNode ( current , sourceFile ) ;
56
48
57
49
if ( discardInitialIndentationIfNextTokenIsOpenOrCloseBrace ( precedingToken , current , lineAtPosition , sourceFile ) ) {
58
50
indentation = 0 ;
59
51
}
60
52
else {
61
- indentation = isNodeContentIndented ( current , previous ) && lineAtPosition !== currentStartLine ? options . indentSpaces : 0 ;
53
+ indentation = isNodeContentIndented ( current , previous ) && lineAtPosition !== currentStart . line ? options . indentSpaces : 0 ;
62
54
}
63
55
64
56
break ;
65
57
}
66
- var customIndentation = getCustomIndentationForListItem ( current , sourceFile ) ;
67
- if ( customIndentation !== - 1 ) {
68
- return customIndentation ;
69
- }
70
58
71
59
// check if current node is a list item - if yes, take indentation from it
72
- var customIndentation = getCustomIndentationForListItem ( current , sourceFile ) ;
73
- if ( customIndentation !== - 1 ) {
74
- return customIndentation ;
60
+ var actualIndentation = getActualIndentationForListItem ( current , sourceFile , options ) ;
61
+ if ( actualIndentation !== - 1 ) {
62
+ return actualIndentation ;
75
63
}
76
64
77
65
previous = current ;
@@ -85,37 +73,121 @@ module ts.formatting {
85
73
86
74
87
75
var parent : Node = current . parent ;
88
- var parentStartLine : number ;
76
+ var parentStart : LineAndCharacter ;
89
77
90
78
// walk upwards and collect indentations for pairs of parent-child nodes
91
79
// 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'
92
80
while ( parent ) {
93
81
94
82
// check if current node is a list item - if yes, take indentation from it
95
- var customIndentation = getCustomIndentationForListItem ( current , sourceFile ) ;
96
- if ( customIndentation !== - 1 ) {
97
- return customIndentation + indentation ;
83
+ var actualIndentation = getActualIndentationForListItem ( current , sourceFile , options ) ;
84
+ if ( actualIndentation !== - 1 ) {
85
+ return actualIndentation + indentation ;
86
+ }
87
+
88
+ parentStart = sourceFile . getLineAndCharacterFromPosition ( parent . getStart ( sourceFile ) ) ;
89
+ var parentAndChildShareLine =
90
+ parentStart . line === currentStart . line ||
91
+ isChildStartsOnTheSameLineWithElseInIfStatement ( parent , current , currentStart . line , sourceFile ) ;
92
+
93
+ // try to fetch actual indentation for current node from source text
94
+ var actualIndentation = getActualIndentationForNode ( current , parent , currentStart , parentAndChildShareLine , sourceFile , options ) ;
95
+ if ( actualIndentation !== - 1 ) {
96
+ return actualIndentation + indentation ;
98
97
}
99
98
100
- parentStartLine = sourceFile . getLineAndCharacterFromPosition ( parent . getStart ( sourceFile ) ) . line ;
101
99
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
102
- var increaseIndentation =
103
- isNodeContentIndented ( parent , current ) &&
104
- parentStartLine !== currentStartLine &&
105
- ! isChildStartsOnTheSameLineWithElseInIfStatement ( parent , current , currentStartLine , sourceFile ) ;
100
+ var increaseIndentation = isNodeContentIndented ( parent , current ) && ! parentAndChildShareLine ;
106
101
107
102
if ( increaseIndentation ) {
108
103
indentation += options . indentSpaces ;
109
104
}
110
105
111
106
current = parent ;
112
- currentStartLine = parentStartLine ;
107
+ currentStart = parentStart ;
113
108
parent = current . parent ;
114
109
}
115
110
116
111
return indentation ;
117
112
}
118
113
114
+ function isDeclaration ( n : Node ) : boolean {
115
+ switch ( n . kind ) {
116
+ case SyntaxKind . ClassDeclaration :
117
+ case SyntaxKind . EnumDeclaration :
118
+ case SyntaxKind . FunctionDeclaration :
119
+ case SyntaxKind . ImportDeclaration :
120
+ case SyntaxKind . Method :
121
+ case SyntaxKind . Property :
122
+ case SyntaxKind . ModuleDeclaration :
123
+ case SyntaxKind . InterfaceDeclaration :
124
+ case SyntaxKind . VariableDeclaration :
125
+ return true ;
126
+ default :
127
+ return false ;
128
+ }
129
+ }
130
+
131
+ function isStatement ( n : Node ) : boolean {
132
+ switch ( n . kind ) {
133
+ case SyntaxKind . BreakStatement :
134
+ case SyntaxKind . ContinueStatement :
135
+ case SyntaxKind . DebuggerStatement :
136
+ case SyntaxKind . DoStatement :
137
+ case SyntaxKind . ExpressionStatement :
138
+ case SyntaxKind . EmptyStatement :
139
+ case SyntaxKind . ForInStatement :
140
+ case SyntaxKind . ForStatement :
141
+ case SyntaxKind . IfStatement :
142
+ case SyntaxKind . LabelledStatement :
143
+ case SyntaxKind . ReturnStatement :
144
+ case SyntaxKind . SwitchStatement :
145
+ case SyntaxKind . ThrowKeyword :
146
+ case SyntaxKind . TryStatement :
147
+ case SyntaxKind . VariableStatement :
148
+ case SyntaxKind . WhileStatement :
149
+ case SyntaxKind . WithStatement :
150
+ return true ;
151
+ default :
152
+ return false ;
153
+ }
154
+ }
155
+
156
+ function getActualIndentationForListItemBeforeComma ( commaToken : Node , sourceFile : SourceFile , options : TypeScript . FormattingOptions ) : number {
157
+ // previous token is comma that separates items in list - find the previous item and try to derive indentation from it
158
+ var precedingListItem = findPrecedingListItem ( commaToken ) ;
159
+ var precedingListItemStartLineAndChar = sourceFile . getLineAndCharacterFromPosition ( precedingListItem . getStart ( sourceFile ) ) ;
160
+ var listStart = getStartLineAndCharacterForNode ( precedingListItem . parent , sourceFile ) ;
161
+
162
+ if ( precedingListItemStartLineAndChar . line !== listStart . line ) {
163
+ return findColumnForFirstNonWhitespaceCharacterInLine ( precedingListItemStartLineAndChar , sourceFile , options ) ;
164
+ // previous list item starts on the different line with list, find first non-whitespace character in this line and use its position as indentation
165
+ var lineStartPosition = sourceFile . getPositionFromLineAndCharacter ( precedingListItemStartLineAndChar . line , 1 ) ;
166
+ for ( var i = 0 ; i < precedingListItemStartLineAndChar . character ; ++ i ) {
167
+ if ( ! isWhiteSpace ( sourceFile . text . charCodeAt ( lineStartPosition + i ) ) ) {
168
+ return i ;
169
+ }
170
+ }
171
+
172
+ // seems that this is the first non-whitespace character on the line - return it
173
+ return precedingListItemStartLineAndChar . character ;
174
+ }
175
+
176
+ return - 1 ;
177
+ }
178
+
179
+ function getActualIndentationForNode ( current : Node , parent : Node , currentLineAndChar : LineAndCharacter , parentAndChildShareLine : boolean , sourceFile : SourceFile , options : TypeScript . FormattingOptions ) : number {
180
+ var useActualIndentation =
181
+ ( isDeclaration ( current ) || isStatement ( current ) ) &&
182
+ ( parent . kind === SyntaxKind . SourceFile || ! parentAndChildShareLine ) ;
183
+
184
+ if ( ! useActualIndentation ) {
185
+ return - 1 ;
186
+ }
187
+
188
+ return findColumnForFirstNonWhitespaceCharacterInLine ( currentLineAndChar , sourceFile , options ) ;
189
+ }
190
+
119
191
function discardInitialIndentationIfNextTokenIsOpenOrCloseBrace ( precedingToken : Node , current : Node , lineAtPosition : number , sourceFile : SourceFile ) : boolean {
120
192
var nextToken = findNextToken ( precedingToken , current ) ;
121
193
if ( ! nextToken ) {
@@ -136,15 +208,15 @@ module ts.formatting {
136
208
// class A {
137
209
// $}
138
210
139
- var nextTokenStartLine = getStartLineForNode ( nextToken , sourceFile ) ;
211
+ var nextTokenStartLine = getStartLineAndCharacterForNode ( nextToken , sourceFile ) . line ;
140
212
return lineAtPosition === nextTokenStartLine ;
141
213
}
142
214
143
215
return false ;
144
216
}
145
217
146
- function getStartLineForNode ( n : Node , sourceFile : SourceFile ) : number {
147
- return sourceFile . getLineAndCharacterFromPosition ( n . getStart ( sourceFile ) ) . line ;
218
+ function getStartLineAndCharacterForNode ( n : Node , sourceFile : SourceFile ) : LineAndCharacter {
219
+ return sourceFile . getLineAndCharacterFromPosition ( n . getStart ( sourceFile ) ) ;
148
220
}
149
221
150
222
function findPrecedingListItem ( commaToken : Node ) : Node {
@@ -174,39 +246,39 @@ module ts.formatting {
174
246
var elseKeyword = forEach ( parent . getChildren ( ) , c => c . kind === SyntaxKind . ElseKeyword && c ) ;
175
247
Debug . assert ( elseKeyword ) ;
176
248
177
- var elseKeywordStartLine = getStartLineForNode ( elseKeyword , sourceFile ) ;
249
+ var elseKeywordStartLine = getStartLineAndCharacterForNode ( elseKeyword , sourceFile ) . line ;
178
250
return elseKeywordStartLine === childStartLine ;
179
251
}
180
252
}
181
253
182
- function getCustomIndentationForListItem ( node : Node , sourceFile : SourceFile ) : number {
254
+ function getActualIndentationForListItem ( node : Node , sourceFile : SourceFile , options : TypeScript . FormattingOptions ) : number {
183
255
if ( node . parent ) {
184
256
switch ( node . parent . kind ) {
185
257
case SyntaxKind . ObjectLiteral :
186
- return getCustomIndentationFromList ( ( < ObjectLiteral > node . parent ) . properties ) ;
258
+ return getActualIndentationFromList ( ( < ObjectLiteral > node . parent ) . properties ) ;
187
259
case SyntaxKind . TypeLiteral :
188
- return getCustomIndentationFromList ( ( < TypeLiteralNode > node . parent ) . members ) ;
260
+ return getActualIndentationFromList ( ( < TypeLiteralNode > node . parent ) . members ) ;
189
261
case SyntaxKind . ArrayLiteral :
190
- return getCustomIndentationFromList ( ( < ArrayLiteral > node . parent ) . elements ) ;
262
+ return getActualIndentationFromList ( ( < ArrayLiteral > node . parent ) . elements ) ;
191
263
case SyntaxKind . FunctionDeclaration :
192
264
case SyntaxKind . FunctionExpression :
193
265
case SyntaxKind . ArrowFunction :
194
266
case SyntaxKind . Method :
195
267
case SyntaxKind . CallSignature :
196
268
case SyntaxKind . ConstructSignature :
197
269
if ( ( < SignatureDeclaration > node . parent ) . typeParameters && node . end < ( < SignatureDeclaration > node . parent ) . typeParameters . end ) {
198
- return getCustomIndentationFromList ( ( < SignatureDeclaration > node . parent ) . typeParameters ) ;
270
+ return getActualIndentationFromList ( ( < SignatureDeclaration > node . parent ) . typeParameters ) ;
199
271
}
200
272
else {
201
- return getCustomIndentationFromList ( ( < SignatureDeclaration > node . parent ) . parameters ) ;
273
+ return getActualIndentationFromList ( ( < SignatureDeclaration > node . parent ) . parameters ) ;
202
274
}
203
275
case SyntaxKind . NewExpression :
204
276
case SyntaxKind . CallExpression :
205
277
if ( ( < CallExpression > node . parent ) . typeArguments && node . end < ( < CallExpression > node . parent ) . typeArguments . end ) {
206
- return getCustomIndentationFromList ( ( < CallExpression > node . parent ) . typeArguments ) ;
278
+ return getActualIndentationFromList ( ( < CallExpression > node . parent ) . typeArguments ) ;
207
279
}
208
280
else {
209
- return getCustomIndentationFromList ( ( < CallExpression > node . parent ) . arguments ) ;
281
+ return getActualIndentationFromList ( ( < CallExpression > node . parent ) . arguments ) ;
210
282
}
211
283
212
284
break ;
@@ -215,31 +287,40 @@ module ts.formatting {
215
287
216
288
return - 1 ;
217
289
218
- function getCustomIndentationFromList ( list : Node [ ] ) : number {
290
+ function getActualIndentationFromList ( list : Node [ ] ) : number {
219
291
var index = indexOf ( list , node ) ;
220
292
if ( index !== - 1 ) {
221
- var lineAndCol = sourceFile . getLineAndCharacterFromPosition ( node . getStart ( sourceFile ) ) ;
293
+ var lineAndCharacter = getStartLineAndCharacterForNode ( node , sourceFile ) ; ;
222
294
for ( var i = index - 1 ; i >= 0 ; -- i ) {
223
- var prevLineAndCol = sourceFile . getLineAndCharacterFromPosition ( list [ i ] . getStart ( sourceFile ) ) ;
224
- if ( lineAndCol . line !== prevLineAndCol . line ) {
225
- return findFirstNonWhitespaceCharacterInLine ( lineAndCol . line , lineAndCol . character , sourceFile ) ;
295
+ var prevLineAndCharacter = getStartLineAndCharacterForNode ( list [ i ] , sourceFile ) ;
296
+ if ( lineAndCharacter . line !== prevLineAndCharacter . line ) {
297
+ return findColumnForFirstNonWhitespaceCharacterInLine ( lineAndCharacter , sourceFile , options ) ;
226
298
}
227
- lineAndCol = prevLineAndCol ;
299
+ lineAndCharacter = prevLineAndCharacter ;
228
300
}
229
301
}
230
302
return - 1 ;
231
303
}
232
304
}
233
305
234
- function findFirstNonWhitespaceCharacterInLine ( line : number , maxCharacter : number , sourceFile : SourceFile ) : number {
235
- var lineStart = sourceFile . getPositionFromLineAndCharacter ( line , 1 ) ;
236
- for ( var i = 0 ; i < maxCharacter ; ++ i ) {
237
- if ( ! isWhiteSpace ( sourceFile . text . charCodeAt ( lineStart + i ) ) ) {
238
- return i ;
306
+ function findColumnForFirstNonWhitespaceCharacterInLine ( lineAndCharacter : LineAndCharacter , sourceFile : SourceFile , options : TypeScript . FormattingOptions ) : number {
307
+ var lineStart = sourceFile . getPositionFromLineAndCharacter ( lineAndCharacter . line , 1 ) ;
308
+ var column = 0 ;
309
+ for ( var i = 0 ; i < lineAndCharacter . character ; ++ i ) {
310
+ var charCode = sourceFile . text . charCodeAt ( lineStart + i ) ;
311
+ if ( ! isWhiteSpace ( charCode ) ) {
312
+ return column ;
313
+ }
314
+
315
+ if ( charCode === CharacterCodes . tab ) {
316
+ column += options . spacesPerTab ;
317
+ }
318
+ else {
319
+ column ++ ;
239
320
}
240
321
}
241
322
242
- return maxCharacter ;
323
+ return column ;
243
324
}
244
325
245
326
function findNextToken ( previousToken : Node , parent : Node ) : Node {
@@ -257,10 +338,10 @@ module ts.formatting {
257
338
var shouldDiveInChildNode =
258
339
// previous token is enclosed somewhere in the child
259
340
( child . pos <= previousToken . pos && child . end > previousToken . end ) ||
260
- // previous token end exactly at the beginning of child
341
+ // previous token ends exactly at the beginning of child
261
342
( child . pos === previousToken . end ) ;
262
343
263
- if ( shouldDiveInChildNode && isCandidateNode ( child ) ) {
344
+ if ( shouldDiveInChildNode && isCandidateNode ( child ) ) {
264
345
return find ( child ) ;
265
346
}
266
347
}
0 commit comments