33use crate :: matching:: Var ;
44use crate :: { resolving:: ResolvedRule , Match , SsrMatches } ;
55use ra_syntax:: ast:: { self , AstToken } ;
6- use ra_syntax:: { SyntaxElement , SyntaxKind , SyntaxNode , SyntaxToken , TextSize } ;
6+ use ra_syntax:: { SyntaxElement , SyntaxKind , SyntaxNode , SyntaxToken , TextRange , TextSize } ;
77use ra_text_edit:: TextEdit ;
8+ use rustc_hash:: { FxHashMap , FxHashSet } ;
89
910/// Returns a text edit that will replace each match in `matches` with its corresponding replacement
1011/// template. Placeholders in the template will have been substituted with whatever they matched to
@@ -38,62 +39,79 @@ struct ReplacementRenderer<'a> {
3839 file_src : & ' a str ,
3940 rules : & ' a [ ResolvedRule ] ,
4041 rule : & ' a ResolvedRule ,
42+ out : String ,
43+ // Map from a range within `out` to a token in `template` that represents a placeholder. This is
44+ // used to validate that the generated source code doesn't split any placeholder expansions (see
45+ // below).
46+ placeholder_tokens_by_range : FxHashMap < TextRange , SyntaxToken > ,
47+ // Which placeholder tokens need to be wrapped in parenthesis in order to ensure that when `out`
48+ // is parsed, placeholders don't get split. e.g. if a template of `$a.to_string()` results in `1
49+ // + 2.to_string()` then the placeholder value `1 + 2` was split and needs parenthesis.
50+ placeholder_tokens_requiring_parenthesis : FxHashSet < SyntaxToken > ,
4151}
4252
4353fn render_replace ( match_info : & Match , file_src : & str , rules : & [ ResolvedRule ] ) -> String {
44- let mut out = String :: new ( ) ;
4554 let rule = & rules[ match_info. rule_index ] ;
4655 let template = rule
4756 . template
4857 . as_ref ( )
4958 . expect ( "You called MatchFinder::edits after calling MatchFinder::add_search_pattern" ) ;
50- let renderer = ReplacementRenderer { match_info, file_src, rules, rule } ;
51- renderer. render_node ( & template. node , & mut out) ;
59+ let mut renderer = ReplacementRenderer {
60+ match_info,
61+ file_src,
62+ rules,
63+ rule,
64+ out : String :: new ( ) ,
65+ placeholder_tokens_requiring_parenthesis : FxHashSet :: default ( ) ,
66+ placeholder_tokens_by_range : FxHashMap :: default ( ) ,
67+ } ;
68+ renderer. render_node ( & template. node ) ;
69+ renderer. maybe_rerender_with_extra_parenthesis ( & template. node ) ;
5270 for comment in & match_info. ignored_comments {
53- out. push_str ( & comment. syntax ( ) . to_string ( ) ) ;
71+ renderer . out . push_str ( & comment. syntax ( ) . to_string ( ) ) ;
5472 }
55- out
73+ renderer . out
5674}
5775
5876impl ReplacementRenderer < ' _ > {
59- fn render_node_children ( & self , node : & SyntaxNode , out : & mut String ) {
77+ fn render_node_children ( & mut self , node : & SyntaxNode ) {
6078 for node_or_token in node. children_with_tokens ( ) {
61- self . render_node_or_token ( & node_or_token, out ) ;
79+ self . render_node_or_token ( & node_or_token) ;
6280 }
6381 }
6482
65- fn render_node_or_token ( & self , node_or_token : & SyntaxElement , out : & mut String ) {
83+ fn render_node_or_token ( & mut self , node_or_token : & SyntaxElement ) {
6684 match node_or_token {
6785 SyntaxElement :: Token ( token) => {
68- self . render_token ( & token, out ) ;
86+ self . render_token ( & token) ;
6987 }
7088 SyntaxElement :: Node ( child_node) => {
71- self . render_node ( & child_node, out ) ;
89+ self . render_node ( & child_node) ;
7290 }
7391 }
7492 }
7593
76- fn render_node ( & self , node : & SyntaxNode , out : & mut String ) {
94+ fn render_node ( & mut self , node : & SyntaxNode ) {
7795 use ra_syntax:: ast:: AstNode ;
7896 if let Some ( mod_path) = self . match_info . rendered_template_paths . get ( & node) {
79- out. push_str ( & mod_path. to_string ( ) ) ;
97+ self . out . push_str ( & mod_path. to_string ( ) ) ;
8098 // Emit everything except for the segment's name-ref, since we already effectively
8199 // emitted that as part of `mod_path`.
82100 if let Some ( path) = ast:: Path :: cast ( node. clone ( ) ) {
83101 if let Some ( segment) = path. segment ( ) {
84102 for node_or_token in segment. syntax ( ) . children_with_tokens ( ) {
85103 if node_or_token. kind ( ) != SyntaxKind :: NAME_REF {
86- self . render_node_or_token ( & node_or_token, out ) ;
104+ self . render_node_or_token ( & node_or_token) ;
87105 }
88106 }
89107 }
90108 }
91109 } else {
92- self . render_node_children ( & node, out ) ;
110+ self . render_node_children ( & node) ;
93111 }
94112 }
95113
96- fn render_token ( & self , token : & SyntaxToken , out : & mut String ) {
114+ fn render_token ( & mut self , token : & SyntaxToken ) {
97115 if let Some ( placeholder) = self . rule . get_placeholder ( & token) {
98116 if let Some ( placeholder_value) =
99117 self . match_info . placeholder_values . get ( & Var ( placeholder. ident . to_string ( ) ) )
@@ -107,8 +125,23 @@ impl ReplacementRenderer<'_> {
107125 range. start ( ) ,
108126 self . rules ,
109127 ) ;
128+ let needs_parenthesis =
129+ self . placeholder_tokens_requiring_parenthesis . contains ( token) ;
110130 edit. apply ( & mut matched_text) ;
111- out. push_str ( & matched_text) ;
131+ if needs_parenthesis {
132+ self . out . push ( '(' ) ;
133+ }
134+ self . placeholder_tokens_by_range . insert (
135+ TextRange :: new (
136+ TextSize :: of ( & self . out ) ,
137+ TextSize :: of ( & self . out ) + TextSize :: of ( & matched_text) ,
138+ ) ,
139+ token. clone ( ) ,
140+ ) ;
141+ self . out . push_str ( & matched_text) ;
142+ if needs_parenthesis {
143+ self . out . push ( ')' ) ;
144+ }
112145 } else {
113146 // We validated that all placeholder references were valid before we
114147 // started, so this shouldn't happen.
@@ -118,7 +151,44 @@ impl ReplacementRenderer<'_> {
118151 ) ;
119152 }
120153 } else {
121- out. push_str ( token. text ( ) . as_str ( ) ) ;
154+ self . out . push_str ( token. text ( ) . as_str ( ) ) ;
155+ }
156+ }
157+
158+ // Checks if the resulting code, when parsed doesn't split any placeholders due to different
159+ // order of operations between the search pattern and the replacement template. If any do, then
160+ // we rerender the template and wrap the problematic placeholders with parenthesis.
161+ fn maybe_rerender_with_extra_parenthesis ( & mut self , template : & SyntaxNode ) {
162+ if let Some ( node) = parse_as_kind ( & self . out , template. kind ( ) ) {
163+ self . remove_node_ranges ( node) ;
164+ if self . placeholder_tokens_by_range . is_empty ( ) {
165+ return ;
166+ }
167+ self . placeholder_tokens_requiring_parenthesis =
168+ self . placeholder_tokens_by_range . values ( ) . cloned ( ) . collect ( ) ;
169+ self . out . clear ( ) ;
170+ self . render_node ( template) ;
171+ }
172+ }
173+
174+ fn remove_node_ranges ( & mut self , node : SyntaxNode ) {
175+ self . placeholder_tokens_by_range . remove ( & node. text_range ( ) ) ;
176+ for child in node. children ( ) {
177+ self . remove_node_ranges ( child) ;
178+ }
179+ }
180+ }
181+
182+ fn parse_as_kind ( code : & str , kind : SyntaxKind ) -> Option < SyntaxNode > {
183+ use ra_syntax:: ast:: AstNode ;
184+ if ast:: Expr :: can_cast ( kind) {
185+ if let Ok ( expr) = ast:: Expr :: parse ( code) {
186+ return Some ( expr. syntax ( ) . clone ( ) ) ;
187+ }
188+ } else if ast:: Item :: can_cast ( kind) {
189+ if let Ok ( item) = ast:: Item :: parse ( code) {
190+ return Some ( item. syntax ( ) . clone ( ) ) ;
122191 }
123192 }
193+ None
124194}
0 commit comments