@@ -74,7 +74,7 @@ void FormatTokenLexer::tryMergePreviousTokens() {
74
74
if (Style.isCSharp ()) {
75
75
if (tryMergeCSharpKeywordVariables ())
76
76
return ;
77
- if (tryMergeCSharpVerbatimStringLiteral ())
77
+ if (tryMergeCSharpStringLiteral ())
78
78
return ;
79
79
if (tryMergeCSharpDoubleQuestion ())
80
80
return ;
@@ -181,18 +181,68 @@ bool FormatTokenLexer::tryMergeJSPrivateIdentifier() {
181
181
// Search for verbatim or interpolated string literals @"ABC" or
182
182
// $"aaaaa{abc}aaaaa" i and mark the token as TT_CSharpStringLiteral, and to
183
183
// prevent splitting of @, $ and ".
184
- bool FormatTokenLexer::tryMergeCSharpVerbatimStringLiteral () {
184
+ bool FormatTokenLexer::tryMergeCSharpStringLiteral () {
185
185
if (Tokens.size () < 2 )
186
186
return false ;
187
187
188
- auto &String = *(Tokens.end () - 1 );
189
- if (!String->is (tok::string_literal))
190
- return false ;
188
+ auto &CSharpStringLiteral = *(Tokens.end () - 2 );
189
+
190
+ // Interpolated strings could contain { } with " characters inside.
191
+ // $"{x ?? "null"}"
192
+ // should not be split into $"{x ?? ", null, "}" but should treated as a
193
+ // single string-literal.
194
+ //
195
+ // We opt not to try and format expressions inside {} within a C#
196
+ // interpolated string. Formatting expressions within an interpolated string
197
+ // would require similar work as that done for JavaScript template strings
198
+ // in `handleTemplateStrings()`.
199
+ auto &CSharpInterpolatedString = *(Tokens.end () - 2 );
200
+ if (CSharpInterpolatedString->Type == TT_CSharpStringLiteral &&
201
+ (CSharpInterpolatedString->TokenText .startswith (R"( $")" ) ||
202
+ CSharpInterpolatedString->TokenText .startswith (R"( $@")" ))) {
203
+ int UnmatchedOpeningBraceCount = 0 ;
204
+
205
+ auto TokenTextSize = CSharpInterpolatedString->TokenText .size ();
206
+ for (size_t Index = 0 ; Index < TokenTextSize; ++Index) {
207
+ char C = CSharpInterpolatedString->TokenText [Index];
208
+ if (C == ' {' ) {
209
+ // "{{" inside an interpolated string is an escaped '{' so skip it.
210
+ if (Index + 1 < TokenTextSize &&
211
+ CSharpInterpolatedString->TokenText [Index + 1 ] == ' {' ) {
212
+ ++Index;
213
+ continue ;
214
+ }
215
+ ++UnmatchedOpeningBraceCount;
216
+ } else if (C == ' }' ) {
217
+ // "}}" inside an interpolated string is an escaped '}' so skip it.
218
+ if (Index + 1 < TokenTextSize &&
219
+ CSharpInterpolatedString->TokenText [Index + 1 ] == ' }' ) {
220
+ ++Index;
221
+ continue ;
222
+ }
223
+ --UnmatchedOpeningBraceCount;
224
+ }
225
+ }
226
+
227
+ if (UnmatchedOpeningBraceCount > 0 ) {
228
+ auto &NextToken = *(Tokens.end () - 1 );
229
+ CSharpInterpolatedString->TokenText =
230
+ StringRef (CSharpInterpolatedString->TokenText .begin (),
231
+ NextToken->TokenText .end () -
232
+ CSharpInterpolatedString->TokenText .begin ());
233
+ CSharpInterpolatedString->ColumnWidth += NextToken->ColumnWidth ;
234
+ Tokens.erase (Tokens.end () - 1 );
235
+ return true ;
236
+ }
237
+ }
191
238
192
239
// verbatim strings could contain "" which C# sees as an escaped ".
193
240
// @"""Hello""" will have been tokenized as @"" "Hello" "" and needs
194
241
// merging into a single string literal.
195
- auto &CSharpStringLiteral = *(Tokens.end () - 2 );
242
+ auto &String = *(Tokens.end () - 1 );
243
+ if (!String->is (tok::string_literal))
244
+ return false ;
245
+
196
246
if (CSharpStringLiteral->Type == TT_CSharpStringLiteral &&
197
247
(CSharpStringLiteral->TokenText .startswith (R"( @")" ) ||
198
248
CSharpStringLiteral->TokenText .startswith (R"( $@")" ))) {
0 commit comments