11//! A higher level attributes based on TokenTree, with also some shortcuts.
2- use std:: { fmt, ops} ;
2+ use std:: { borrow :: Cow , fmt, ops} ;
33
44use base_db:: CrateId ;
55use cfg:: CfgExpr ;
@@ -8,6 +8,7 @@ use intern::Interned;
88use mbe:: { syntax_node_to_token_tree, DelimiterKind , Punct } ;
99use smallvec:: { smallvec, SmallVec } ;
1010use span:: { Span , SyntaxContextId } ;
11+ use syntax:: unescape;
1112use syntax:: { ast, format_smolstr, match_ast, AstNode , AstToken , SmolStr , SyntaxNode } ;
1213use triomphe:: ThinArc ;
1314
@@ -54,8 +55,7 @@ impl RawAttrs {
5455 Attr {
5556 id,
5657 input : Some ( Interned :: new ( AttrInput :: Literal ( tt:: Literal {
57- // FIXME: Escape quotes from comment content
58- text : SmolStr :: new ( format_smolstr ! ( "\" {doc}\" " , ) ) ,
58+ text : SmolStr :: new ( format_smolstr ! ( "\" {}\" " , Self :: escape_chars( doc) ) ) ,
5959 span,
6060 } ) ) ) ,
6161 path : Interned :: new ( ModPath :: from ( crate :: name!( doc) ) ) ,
@@ -74,6 +74,10 @@ impl RawAttrs {
7474 RawAttrs { entries }
7575 }
7676
77+ fn escape_chars ( s : & str ) -> String {
78+ s. replace ( '\\' , r#"\\"# ) . replace ( '"' , r#"\""# )
79+ }
80+
7781 pub fn from_attrs_owner (
7882 db : & dyn ExpandDatabase ,
7983 owner : InFile < & dyn ast:: HasAttrs > ,
@@ -297,6 +301,18 @@ impl Attr {
297301 }
298302 }
299303
304+ pub fn string_value_unescape ( & self ) -> Option < Cow < ' _ , str > > {
305+ match self . input . as_deref ( ) ? {
306+ AttrInput :: Literal ( it) => match it. text . strip_prefix ( 'r' ) {
307+ Some ( it) => {
308+ it. trim_matches ( '#' ) . strip_prefix ( '"' ) ?. strip_suffix ( '"' ) . map ( Cow :: Borrowed )
309+ }
310+ None => it. text . strip_prefix ( '"' ) ?. strip_suffix ( '"' ) . and_then ( unescape) ,
311+ } ,
312+ _ => None ,
313+ }
314+ }
315+
300316 /// #[path(ident)]
301317 pub fn single_ident_value ( & self ) -> Option < & tt:: Ident > {
302318 match self . input . as_deref ( ) ? {
@@ -346,6 +362,33 @@ impl Attr {
346362 }
347363}
348364
365+ fn unescape ( s : & str ) -> Option < Cow < ' _ , str > > {
366+ let mut buf = String :: new ( ) ;
367+ let mut prev_end = 0 ;
368+ let mut has_error = false ;
369+ unescape:: unescape_unicode ( s, unescape:: Mode :: Str , & mut |char_range, unescaped_char| match (
370+ unescaped_char,
371+ buf. capacity ( ) == 0 ,
372+ ) {
373+ ( Ok ( c) , false ) => buf. push ( c) ,
374+ ( Ok ( _) , true ) if char_range. len ( ) == 1 && char_range. start == prev_end => {
375+ prev_end = char_range. end
376+ }
377+ ( Ok ( c) , true ) => {
378+ buf. reserve_exact ( s. len ( ) ) ;
379+ buf. push_str ( & s[ ..prev_end] ) ;
380+ buf. push ( c) ;
381+ }
382+ ( Err ( _) , _) => has_error = true ,
383+ } ) ;
384+
385+ match ( has_error, buf. capacity ( ) == 0 ) {
386+ ( true , _) => None ,
387+ ( false , false ) => Some ( Cow :: Owned ( buf) ) ,
388+ ( false , true ) => Some ( Cow :: Borrowed ( s) ) ,
389+ }
390+ }
391+
349392pub fn collect_attrs (
350393 owner : & dyn ast:: HasAttrs ,
351394) -> impl Iterator < Item = ( AttrId , Either < ast:: Attr , ast:: Comment > ) > {
0 commit comments