@@ -173,41 +173,57 @@ where
173
173
174
174
fn unquote_block_string ( src : & str ) -> Result < String , Error < Token < ' _ > , Token < ' _ > > > {
175
175
debug_assert ! ( src. starts_with( "\" \" \" " ) && src. ends_with( "\" \" \" " ) ) ;
176
- let indent = src[ 3 ..src. len ( ) - 3 ]
177
- . lines ( )
178
- . skip ( 1 )
179
- . filter_map ( |line| {
180
- let trimmed = line. trim_start ( ) . len ( ) ;
181
- if trimmed > 0 {
182
- Some ( line. len ( ) - trimmed)
183
- } else {
184
- None // skip whitespace-only lines
185
- }
186
- } )
187
- . min ( )
188
- . unwrap_or ( 0 ) ;
189
- let mut result = String :: with_capacity ( src. len ( ) - 6 ) ;
190
- let mut lines = src[ 3 ..src. len ( ) - 3 ] . lines ( ) ;
191
- if let Some ( first) = lines. next ( ) {
192
- let stripped = first. trim ( ) ;
193
- if !stripped. is_empty ( ) {
194
- result. push_str ( stripped) ;
195
- result. push ( '\n' ) ;
176
+ let lines = src[ 3 ..src. len ( ) - 3 ] . lines ( ) ;
177
+
178
+ let mut common_indent = usize:: MAX ;
179
+ let mut first_non_empty_line: Option < usize > = None ;
180
+ let mut last_non_empty_line = 0 ;
181
+ for ( idx, line) in lines. clone ( ) . enumerate ( ) {
182
+ let indent = line. len ( ) - line. trim_start ( ) . len ( ) ;
183
+ if indent == line. len ( ) {
184
+ continue ;
196
185
}
197
- }
198
- let mut last_line = 0 ;
199
- for line in lines {
200
- last_line = result . len ( ) ;
201
- if line . len ( ) > indent {
202
- result . push_str ( & line [ indent.. ] . replace ( r#"\""""# , r#"""""# ) ) ;
186
+
187
+ first_non_empty_line . get_or_insert ( idx ) ;
188
+ last_non_empty_line = idx ;
189
+
190
+ if idx != 0 {
191
+ common_indent = std :: cmp :: min ( common_indent , indent ) ;
203
192
}
204
- result. push ( '\n' ) ;
205
193
}
206
- if result[ last_line..] . trim ( ) . is_empty ( ) {
207
- result. truncate ( last_line) ;
194
+
195
+ if first_non_empty_line. is_none ( ) {
196
+ // The block string contains only whitespace.
197
+ return Ok ( "" . to_string ( ) ) ;
208
198
}
199
+ let first_non_empty_line = first_non_empty_line. unwrap ( ) ;
200
+
201
+ let mut result = String :: with_capacity ( src. len ( ) - 6 ) ;
202
+ let mut lines = lines
203
+ . enumerate ( )
204
+ // Skip leading and trailing empty lines.
205
+ . skip ( first_non_empty_line)
206
+ . take ( last_non_empty_line - first_non_empty_line + 1 )
207
+ // Remove indent, except the first line.
208
+ . map ( |( idx, line) | {
209
+ if idx != 0 && line. len ( ) >= common_indent {
210
+ & line[ common_indent..]
211
+ } else {
212
+ line
213
+ }
214
+ } )
215
+ // Handle escaped triple-quote (\""").
216
+ . map ( |x| x. replace ( r#"\""""# , r#"""""# ) ) ;
217
+
218
+ if let Some ( line) = lines. next ( ) {
219
+ result. push_str ( & line) ;
209
220
210
- Ok ( result)
221
+ for line in lines {
222
+ result. push_str ( "\n " ) ;
223
+ result. push_str ( & line) ;
224
+ }
225
+ }
226
+ return Ok ( result) ;
211
227
}
212
228
213
229
fn unquote_string ( s : & str ) -> Result < String , Error < Token , Token > > {
@@ -390,6 +406,7 @@ where
390
406
391
407
#[ cfg( test) ]
392
408
mod tests {
409
+ use super :: unquote_block_string;
393
410
use super :: unquote_string;
394
411
use super :: Number ;
395
412
@@ -422,4 +439,43 @@ mod tests {
422
439
"\u{0009} hello \u{000A} there"
423
440
) ;
424
441
}
442
+
443
+ #[ test]
444
+ fn block_string_leading_and_trailing_empty_lines ( ) {
445
+ let block = & triple_quote ( " \n \n Hello,\n World!\n \n Yours,\n GraphQL.\n \n \n " ) ;
446
+ assert_eq ! (
447
+ unquote_block_string( & block) ,
448
+ Result :: Ok ( "Hello,\n World!\n \n Yours,\n GraphQL." . to_string( ) )
449
+ ) ;
450
+ }
451
+
452
+ #[ test]
453
+ fn block_string_indent ( ) {
454
+ let block = & triple_quote ( "Hello \n \n Hello,\n World!\n " ) ;
455
+ assert_eq ! (
456
+ unquote_block_string( & block) ,
457
+ Result :: Ok ( "Hello \n \n Hello,\n World!" . to_string( ) )
458
+ ) ;
459
+ }
460
+
461
+ #[ test]
462
+ fn block_string_escaping ( ) {
463
+ let block = triple_quote ( r#"\""""# ) ;
464
+ assert_eq ! (
465
+ unquote_block_string( & block) ,
466
+ Result :: Ok ( "\" \" \" " . to_string( ) )
467
+ ) ;
468
+ }
469
+
470
+ #[ test]
471
+ fn block_string_empty ( ) {
472
+ let block = triple_quote ( "" ) ;
473
+ assert_eq ! ( unquote_block_string( & block) , Result :: Ok ( "" . to_string( ) ) ) ;
474
+ let block = triple_quote ( " \n \t \n " ) ;
475
+ assert_eq ! ( unquote_block_string( & block) , Result :: Ok ( "" . to_string( ) ) ) ;
476
+ }
477
+
478
+ fn triple_quote ( input : & str ) -> String {
479
+ return format ! ( "\" \" \" {}\" \" \" " , input) ;
480
+ }
425
481
}
0 commit comments