@@ -263,9 +263,22 @@ fn strip_string_quotes(node: &Node, source: &[u8]) -> String {
263263 if let Some ( content) = find_child ( node, "string_content" ) {
264264 return node_text ( & content, source) . to_string ( ) ;
265265 }
266- node_text ( node, source)
267- . trim_matches ( |c| c == '\'' || c == '"' )
268- . to_string ( )
266+ // Fallback: strip exactly one matching quote from each end. We can't use
267+ // `trim_matches` because it strips *all* matching characters greedily —
268+ // e.g. for the literal `"'"` (a string containing a single quote) the
269+ // text is `"`, `'`, `"`, and `trim_matches` would consume all three,
270+ // returning an empty string. Index-based strip removes only the outer
271+ // pair, leaving the inner character intact.
272+ let text = node_text ( node, source) ;
273+ let bytes = text. as_bytes ( ) ;
274+ if bytes. len ( ) >= 2 {
275+ let first = bytes[ 0 ] ;
276+ let last = bytes[ bytes. len ( ) - 1 ] ;
277+ if ( first == b'\'' || first == b'"' ) && first == last {
278+ return text[ 1 ..bytes. len ( ) - 1 ] . to_string ( ) ;
279+ }
280+ }
281+ text. to_string ( )
269282}
270283
271284fn handle_library_call ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
@@ -452,6 +465,22 @@ mod tests {
452465 assert_eq ! ( s. imports[ 0 ] . source, "dplyr" ) ;
453466 }
454467
468+ #[ test]
469+ fn source_call_with_mixed_quote_content_preserves_inner_quote ( ) {
470+ // Edge case for the strip_string_quotes fallback: if a grammar
471+ // version drops the `string_content` child, the fallback must strip
472+ // only the outer pair of quotes. `trim_matches` would greedily eat
473+ // both the outer `"` and the inner `'`, returning an empty path.
474+ // Index-based strip leaves the inner `'` intact.
475+ //
476+ // We exercise the fallback indirectly via `source("a'b.R")` —
477+ // current grammars expose `string_content`, so this primarily
478+ // guards against future regressions in the fallback path.
479+ let s = parse_r ( "source(\" a'b.R\" )\n " ) ;
480+ assert_eq ! ( s. imports. len( ) , 1 ) ;
481+ assert_eq ! ( s. imports[ 0 ] . source, "a'b.R" ) ;
482+ }
483+
455484 #[ test]
456485 fn nested_function_assignment_is_recorded ( ) {
457486 // Matches the JS extractor's documented behavior: function
0 commit comments