3
3
use crate :: matching:: Var ;
4
4
use crate :: { resolving:: ResolvedRule , Match , SsrMatches } ;
5
5
use ra_syntax:: ast:: { self , AstToken } ;
6
- use ra_syntax:: { SyntaxElement , SyntaxKind , SyntaxNode , SyntaxToken , TextSize } ;
6
+ use ra_syntax:: { SyntaxElement , SyntaxKind , SyntaxNode , SyntaxToken , TextRange , TextSize } ;
7
7
use ra_text_edit:: TextEdit ;
8
+ use rustc_hash:: { FxHashMap , FxHashSet } ;
8
9
9
10
/// Returns a text edit that will replace each match in `matches` with its corresponding replacement
10
11
/// template. Placeholders in the template will have been substituted with whatever they matched to
@@ -38,62 +39,79 @@ struct ReplacementRenderer<'a> {
38
39
file_src : & ' a str ,
39
40
rules : & ' a [ ResolvedRule ] ,
40
41
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 > ,
41
51
}
42
52
43
53
fn render_replace ( match_info : & Match , file_src : & str , rules : & [ ResolvedRule ] ) -> String {
44
- let mut out = String :: new ( ) ;
45
54
let rule = & rules[ match_info. rule_index ] ;
46
55
let template = rule
47
56
. template
48
57
. as_ref ( )
49
58
. 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 ) ;
52
70
for comment in & match_info. ignored_comments {
53
- out. push_str ( & comment. syntax ( ) . to_string ( ) ) ;
71
+ renderer . out . push_str ( & comment. syntax ( ) . to_string ( ) ) ;
54
72
}
55
- out
73
+ renderer . out
56
74
}
57
75
58
76
impl ReplacementRenderer < ' _ > {
59
- fn render_node_children ( & self , node : & SyntaxNode , out : & mut String ) {
77
+ fn render_node_children ( & mut self , node : & SyntaxNode ) {
60
78
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) ;
62
80
}
63
81
}
64
82
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 ) {
66
84
match node_or_token {
67
85
SyntaxElement :: Token ( token) => {
68
- self . render_token ( & token, out ) ;
86
+ self . render_token ( & token) ;
69
87
}
70
88
SyntaxElement :: Node ( child_node) => {
71
- self . render_node ( & child_node, out ) ;
89
+ self . render_node ( & child_node) ;
72
90
}
73
91
}
74
92
}
75
93
76
- fn render_node ( & self , node : & SyntaxNode , out : & mut String ) {
94
+ fn render_node ( & mut self , node : & SyntaxNode ) {
77
95
use ra_syntax:: ast:: AstNode ;
78
96
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 ( ) ) ;
80
98
// Emit everything except for the segment's name-ref, since we already effectively
81
99
// emitted that as part of `mod_path`.
82
100
if let Some ( path) = ast:: Path :: cast ( node. clone ( ) ) {
83
101
if let Some ( segment) = path. segment ( ) {
84
102
for node_or_token in segment. syntax ( ) . children_with_tokens ( ) {
85
103
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) ;
87
105
}
88
106
}
89
107
}
90
108
}
91
109
} else {
92
- self . render_node_children ( & node, out ) ;
110
+ self . render_node_children ( & node) ;
93
111
}
94
112
}
95
113
96
- fn render_token ( & self , token : & SyntaxToken , out : & mut String ) {
114
+ fn render_token ( & mut self , token : & SyntaxToken ) {
97
115
if let Some ( placeholder) = self . rule . get_placeholder ( & token) {
98
116
if let Some ( placeholder_value) =
99
117
self . match_info . placeholder_values . get ( & Var ( placeholder. ident . to_string ( ) ) )
@@ -107,8 +125,23 @@ impl ReplacementRenderer<'_> {
107
125
range. start ( ) ,
108
126
self . rules ,
109
127
) ;
128
+ let needs_parenthesis =
129
+ self . placeholder_tokens_requiring_parenthesis . contains ( token) ;
110
130
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
+ }
112
145
} else {
113
146
// We validated that all placeholder references were valid before we
114
147
// started, so this shouldn't happen.
@@ -118,7 +151,44 @@ impl ReplacementRenderer<'_> {
118
151
) ;
119
152
}
120
153
} 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 ( ) ) ;
122
191
}
123
192
}
193
+ None
124
194
}
0 commit comments