@@ -274,13 +274,87 @@ impl Expect {
274274 line_start += line. len ( ) ;
275275 }
276276 let ( literal_start, line_indent) = target_line. unwrap ( ) ;
277- let literal_length =
278- file[ literal_start..] . find ( "]]" ) . expect ( "Couldn't find matching `]]` for `expect![[`." ) ;
279- let literal_range = literal_start..literal_start + literal_length;
277+
278+ let lit_to_eof = & file[ literal_start..] ;
279+ let lit_to_eof_trimmed = lit_to_eof. trim_start ( ) ;
280+
281+ let literal_start = literal_start + ( lit_to_eof. len ( ) - lit_to_eof_trimmed. len ( ) ) ;
282+
283+ let literal_len = locate_end ( lit_to_eof_trimmed)
284+ . expect ( "Couldn't find matching `]]` for `expect![[`." ) ;
285+ let literal_range = literal_start..literal_start + literal_len;
280286 Location { line_indent, literal_range }
281287 }
282288}
283289
290+ fn locate_end ( lit_to_eof : & str ) -> Option < usize > {
291+ assert ! ( lit_to_eof. chars( ) . next( ) . map_or( true , |c| !c. is_whitespace( ) ) ) ;
292+
293+ if lit_to_eof. starts_with ( "]]" ) {
294+ // expect![[ ]]
295+ Some ( 0 )
296+ } else {
297+ // expect![["foo"]]
298+ find_str_lit_len ( lit_to_eof)
299+ }
300+ }
301+
302+ /// Parses a string literal, returning the byte index of its last character
303+ /// (either a quote or a hash).
304+ fn find_str_lit_len ( str_lit_to_eof : & str ) -> Option < usize > {
305+ use StrLitKind :: * ;
306+ #[ derive( Clone , Copy ) ]
307+ enum StrLitKind {
308+ Normal ,
309+ Raw ( usize ) ,
310+ }
311+
312+ fn try_find_n_hashes (
313+ s : & mut impl Iterator < Item = char > ,
314+ desired_hashes : usize ,
315+ ) -> Option < ( usize , Option < char > ) > {
316+ let mut n = 0 ;
317+ loop {
318+ match s. next ( ) ? {
319+ '#' => n += 1 ,
320+ c => return Some ( ( n, Some ( c) ) ) ,
321+ }
322+
323+ if n == desired_hashes {
324+ return Some ( ( n, None ) ) ;
325+ }
326+ }
327+ }
328+
329+ let mut s = str_lit_to_eof. chars ( ) ;
330+ let kind = match s. next ( ) ? {
331+ '"' => Normal ,
332+ 'r' => {
333+ let ( n, c) = try_find_n_hashes ( & mut s, usize:: MAX ) ?;
334+ if c != Some ( '"' ) { return None ; }
335+ Raw ( n)
336+ }
337+ _ => return None ,
338+ } ;
339+
340+ let mut oldc = None ;
341+ loop {
342+ let c = oldc. take ( ) . or_else ( || s. next ( ) ) ?;
343+ match ( c, kind) {
344+ ( '\\' , Normal ) => { let _escaped = s. next ( ) ?; }
345+ ( '"' , Normal | Raw ( 0 ) ) => break ,
346+ ( '"' , Raw ( n) ) => {
347+ let ( seen, c) = try_find_n_hashes ( & mut s, n) ?;
348+ if seen == n { break ; }
349+ oldc = c;
350+ }
351+ _ => { }
352+ }
353+ }
354+
355+ Some ( str_lit_to_eof. len ( ) - s. as_str ( ) . len ( ) )
356+ }
357+
284358impl ExpectFile {
285359 /// Checks if file contents is equal to `actual`.
286360 pub fn assert_eq ( & self , actual : & str ) {
@@ -661,4 +735,48 @@ line1
661735"# ] ] ,
662736 ) ;
663737 }
738+
739+ #[ test]
740+ fn test_locate ( ) {
741+ macro_rules! check_locate {
742+ ( $( [ [ $s: literal] ] ) ,* $( , ) ?) => { $( {
743+ let lit = stringify!( $s) ;
744+ let with_trailer = format!( "{} \t ]]\n " , lit) ;
745+ assert_eq!( locate_end( & with_trailer) , Some ( lit. len( ) ) ) ;
746+ } ) * } ;
747+ }
748+
749+ // Check that we handle string literals containing "]]" correctly.
750+ check_locate ! (
751+ [ [ r#"{ arr: [[1, 2], [3, 4]], other: "foo" } "# ] ] ,
752+ [ [ "]]" ] ] ,
753+ [ [ "\" ]]" ] ] ,
754+ [ [ r#""]]"# ] ] ,
755+ ) ;
756+
757+ // Check `expect![[ ]]` as well.
758+ assert_eq ! ( locate_end( "]]" ) , Some ( 0 ) ) ;
759+ }
760+
761+ #[ test]
762+ fn test_find_str_lit_len ( ) {
763+ macro_rules! check_str_lit_len {
764+ ( $( $s: literal ) ,* $( , ) ?) => { $( {
765+ let lit = stringify!( $s) ;
766+ assert_eq!( find_str_lit_len( lit) , Some ( lit. len( ) ) ) ;
767+ } ) * }
768+ }
769+
770+ check_str_lit_len ! [
771+ r##"foa\""#"## ,
772+ r##"
773+
774+ asdf][]]""""#
775+ "## ,
776+ "" ,
777+ "\" " ,
778+ "\" \" " ,
779+ "#\" #\" #" ,
780+ ] ;
781+ }
664782}
0 commit comments