@@ -97,11 +97,41 @@ namespace ts.formatting {
97
97
}
98
98
99
99
export function formatOnSemicolon ( position : number , sourceFile : SourceFile , rulesProvider : RulesProvider , options : FormatCodeSettings ) : TextChange [ ] {
100
- return formatOutermostParent ( position , SyntaxKind . SemicolonToken , sourceFile , options , rulesProvider , FormattingRequestKind . FormatOnSemicolon ) ;
100
+ const semicolon = findImmediatelyPrecedingTokenOfKind ( position , SyntaxKind . SemicolonToken , sourceFile ) ;
101
+ return formatNodeLines ( findOutermostNodeWithinListLevel ( semicolon ) , sourceFile , options , rulesProvider , FormattingRequestKind . FormatOnSemicolon ) ;
102
+ }
103
+
104
+ export function formatOnOpeningCurly ( position : number , sourceFile : SourceFile , rulesProvider : RulesProvider , options : FormatCodeSettings ) : TextChange [ ] {
105
+ const openingCurly = findImmediatelyPrecedingTokenOfKind ( position , SyntaxKind . OpenBraceToken , sourceFile ) ;
106
+ if ( ! openingCurly ) {
107
+ return [ ] ;
108
+ }
109
+ const curlyBraceRange = openingCurly . parent ;
110
+ const outermostNode = findOutermostNodeWithinListLevel ( curlyBraceRange ) ;
111
+
112
+ /**
113
+ * We limit the span to end at the opening curly to handle the case where
114
+ * the brace matched to that just typed will be incorrect after further edits.
115
+ * For example, we could type the opening curly for the following method
116
+ * body without brace-matching activated:
117
+ * ```
118
+ * class C {
119
+ * foo()
120
+ * }
121
+ * ```
122
+ * and we wouldn't want to move the closing brace.
123
+ */
124
+ const textRange : TextRange = {
125
+ pos : getLineStartPositionForPosition ( outermostNode . getStart ( sourceFile ) , sourceFile ) ,
126
+ end : position
127
+ } ;
128
+
129
+ return formatSpan ( textRange , sourceFile , options , rulesProvider , FormattingRequestKind . FormatOnOpeningCurlyBrace ) ;
101
130
}
102
131
103
132
export function formatOnClosingCurly ( position : number , sourceFile : SourceFile , rulesProvider : RulesProvider , options : FormatCodeSettings ) : TextChange [ ] {
104
- return formatOutermostParent ( position , SyntaxKind . CloseBraceToken , sourceFile , options , rulesProvider , FormattingRequestKind . FormatOnClosingCurlyBrace ) ;
133
+ const precedingToken = findImmediatelyPrecedingTokenOfKind ( position , SyntaxKind . CloseBraceToken , sourceFile ) ;
134
+ return formatNodeLines ( findOutermostNodeWithinListLevel ( precedingToken ) , sourceFile , options , rulesProvider , FormattingRequestKind . FormatOnClosingCurlyBrace ) ;
105
135
}
106
136
107
137
export function formatDocument ( sourceFile : SourceFile , rulesProvider : RulesProvider , options : FormatCodeSettings ) : TextChange [ ] {
@@ -121,44 +151,36 @@ namespace ts.formatting {
121
151
return formatSpan ( span , sourceFile , options , rulesProvider , FormattingRequestKind . FormatSelection ) ;
122
152
}
123
153
124
- function formatOutermostParent ( position : number , expectedLastToken : SyntaxKind , sourceFile : SourceFile , options : FormatCodeSettings , rulesProvider : RulesProvider , requestKind : FormattingRequestKind ) : TextChange [ ] {
125
- const parent = findOutermostParent ( position , expectedLastToken , sourceFile ) ;
126
- if ( ! parent ) {
127
- return [ ] ;
128
- }
129
- const span = {
130
- pos : getLineStartPositionForPosition ( parent . getStart ( sourceFile ) , sourceFile ) ,
131
- end : parent . end
132
- } ;
133
- return formatSpan ( span , sourceFile , options , rulesProvider , requestKind ) ;
134
- }
154
+ /**
155
+ * Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment).
156
+ * @param expectedTokenKind The kind of the last token constituting the desired parent node.
157
+ */
158
+ function findImmediatelyPrecedingTokenOfKind ( end : number , expectedTokenKind : SyntaxKind , sourceFile : SourceFile ) : Node | undefined {
159
+ const precedingToken = findPrecedingToken ( end , sourceFile ) ;
135
160
136
- function findOutermostParent ( position : number , expectedTokenKind : SyntaxKind , sourceFile : SourceFile ) : Node {
137
- const precedingToken = findPrecedingToken ( position , sourceFile ) ;
138
-
139
- // when it is claimed that trigger character was typed at given position
140
- // we verify that there is a token with a matching kind whose end is equal to position (because the character was just typed).
141
- // If this condition is not hold - then trigger character was typed in some other context,
142
- // i.e.in comment and thus should not trigger autoformatting
143
- if ( ! precedingToken ||
144
- precedingToken . kind !== expectedTokenKind ||
145
- position !== precedingToken . getEnd ( ) ) {
146
- return undefined ;
147
- }
161
+ return precedingToken && precedingToken . kind === expectedTokenKind && end === precedingToken . getEnd ( ) ?
162
+ precedingToken :
163
+ undefined ;
164
+ }
148
165
149
- // walk up and search for the parent node that ends at the same position with precedingToken.
150
- // for cases like this
151
- //
152
- // let x = 1;
153
- // while (true) {
154
- // }
155
- // after typing close curly in while statement we want to reformat just the while statement.
156
- // However if we just walk upwards searching for the parent that has the same end value -
157
- // we'll end up with the whole source file. isListElement allows to stop on the list element level
158
- let current = precedingToken ;
166
+ /**
167
+ * Finds the highest node enclosing `node` at the same list level as `node`
168
+ * and whose end does not exceed `node.end`.
169
+ *
170
+ * Consider typing the following
171
+ * ```
172
+ * let x = 1;
173
+ * while (true) {
174
+ * }
175
+ * ```
176
+ * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding
177
+ * variable declaration.
178
+ */
179
+ function findOutermostNodeWithinListLevel ( node : Node ) {
180
+ let current = node ;
159
181
while ( current &&
160
182
current . parent &&
161
- current . parent . end === precedingToken . end &&
183
+ current . parent . end === node . end &&
162
184
! isListElement ( current . parent , current ) ) {
163
185
current = current . parent ;
164
186
}
@@ -315,7 +337,7 @@ namespace ts.formatting {
315
337
}
316
338
317
339
/* @internal */
318
- export function formatNode ( node : Node , sourceFileLike : SourceFileLike , languageVariant : LanguageVariant , initialIndentation : number , delta : number , rulesProvider : RulesProvider ) : TextChange [ ] {
340
+ export function formatNodeGivenIndentation ( node : Node , sourceFileLike : SourceFileLike , languageVariant : LanguageVariant , initialIndentation : number , delta : number , rulesProvider : RulesProvider ) : TextChange [ ] {
319
341
const range = { pos : 0 , end : sourceFileLike . text . length } ;
320
342
return formatSpanWorker (
321
343
range ,
@@ -330,6 +352,19 @@ namespace ts.formatting {
330
352
sourceFileLike ) ;
331
353
}
332
354
355
+ function formatNodeLines ( node : Node , sourceFile : SourceFile , options : FormatCodeSettings , rulesProvider : RulesProvider , requestKind : FormattingRequestKind ) : TextChange [ ] {
356
+ if ( ! node ) {
357
+ return [ ] ;
358
+ }
359
+
360
+ const span = {
361
+ pos : getLineStartPositionForPosition ( node . getStart ( sourceFile ) , sourceFile ) ,
362
+ end : node . end
363
+ } ;
364
+
365
+ return formatSpan ( span , sourceFile , options , rulesProvider , requestKind ) ;
366
+ }
367
+
333
368
function formatSpan ( originalRange : TextRange ,
334
369
sourceFile : SourceFile ,
335
370
options : FormatCodeSettings ,
@@ -1096,7 +1131,7 @@ namespace ts.formatting {
1096
1131
return ;
1097
1132
}
1098
1133
1099
- // edit should not be applied only if we have one line feed between elements
1134
+ // edit should not be applied if we have one line feed between elements
1100
1135
const lineDelta = currentStartLine - previousStartLine ;
1101
1136
if ( lineDelta !== 1 ) {
1102
1137
recordReplace ( previousRange . end , currentRange . pos - previousRange . end , options . newLineCharacter ) ;
0 commit comments