@@ -173,41 +173,57 @@ where
173173
174174fn unquote_block_string ( src : & str ) -> Result < String , Error < Token < ' _ > , Token < ' _ > > > {
175175 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 ;
196185 }
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 ) ;
203192 }
204- result. push ( '\n' ) ;
205193 }
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 ( ) ) ;
208198 }
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) ;
209220
210- Ok ( result)
221+ for line in lines {
222+ result. push_str ( "\n " ) ;
223+ result. push_str ( & line) ;
224+ }
225+ }
226+ return Ok ( result) ;
211227}
212228
213229fn unquote_string ( s : & str ) -> Result < String , Error < Token , Token > > {
@@ -390,6 +406,7 @@ where
390406
391407#[ cfg( test) ]
392408mod tests {
409+ use super :: unquote_block_string;
393410 use super :: unquote_string;
394411 use super :: Number ;
395412
@@ -422,4 +439,43 @@ mod tests {
422439 "\u{0009} hello \u{000A} there"
423440 ) ;
424441 }
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+ }
425481}
0 commit comments