1use ide_db::text_edit::TextEdit;
4use ide_db::{FxHashMap, FxHashSet};
5use itertools::Itertools;
6use parser::Edition;
7use syntax::{
8 SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
9 ast::{self, AstNode, AstToken},
10};
11
12use crate::{Match, SsrMatches, fragments, resolving::ResolvedRule};
13
14pub(crate) fn matches_to_edit<'db>(
18 db: &'db dyn hir::db::ExpandDatabase,
19 matches: &SsrMatches,
20 file_src: &str,
21 rules: &[ResolvedRule<'db>],
22) -> TextEdit {
23 matches_to_edit_at_offset(db, matches, file_src, 0.into(), rules)
24}
25
26fn matches_to_edit_at_offset<'db>(
27 db: &'db dyn hir::db::ExpandDatabase,
28 matches: &SsrMatches,
29 file_src: &str,
30 relative_start: TextSize,
31 rules: &[ResolvedRule<'db>],
32) -> TextEdit {
33 let mut edit_builder = TextEdit::builder();
34 for m in &matches.matches {
35 edit_builder.replace(
36 m.range.range.checked_sub(relative_start).unwrap(),
37 render_replace(db, m, file_src, rules, m.range.file_id.edition(db)),
38 );
39 }
40 edit_builder.finish()
41}
42
43struct ReplacementRenderer<'a, 'db> {
44 db: &'db dyn hir::db::ExpandDatabase,
45 match_info: &'a Match,
46 file_src: &'a str,
47 rules: &'a [ResolvedRule<'db>],
48 rule: &'a ResolvedRule<'db>,
49 out: String,
50 placeholder_tokens_by_range: FxHashMap<TextRange, SyntaxToken>,
54 placeholder_tokens_requiring_parenthesis: FxHashSet<SyntaxToken>,
58 edition: Edition,
59}
60
61fn render_replace<'db>(
62 db: &'db dyn hir::db::ExpandDatabase,
63 match_info: &Match,
64 file_src: &str,
65 rules: &[ResolvedRule<'db>],
66 edition: Edition,
67) -> String {
68 let rule = &rules[match_info.rule_index];
69 let template = rule
70 .template
71 .as_ref()
72 .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern");
73 let mut renderer = ReplacementRenderer {
74 db,
75 match_info,
76 file_src,
77 rules,
78 rule,
79 out: String::new(),
80 placeholder_tokens_requiring_parenthesis: FxHashSet::default(),
81 placeholder_tokens_by_range: FxHashMap::default(),
82 edition,
83 };
84 renderer.render_node(&template.node);
85 renderer.maybe_rerender_with_extra_parenthesis(&template.node);
86 for comment in &match_info.ignored_comments {
87 renderer.out.push_str(&comment.syntax().to_string());
88 }
89 renderer.out
90}
91
92impl<'db> ReplacementRenderer<'_, 'db> {
93 fn render_node_children(&mut self, node: &SyntaxNode) {
94 for node_or_token in node.children_with_tokens() {
95 self.render_node_or_token(&node_or_token);
96 }
97 }
98
99 fn render_node_or_token(&mut self, node_or_token: &SyntaxElement) {
100 match node_or_token {
101 SyntaxElement::Token(token) => {
102 self.render_token(token);
103 }
104 SyntaxElement::Node(child_node) => {
105 self.render_node(child_node);
106 }
107 }
108 }
109
110 fn render_node(&mut self, node: &SyntaxNode) {
111 if let Some(mod_path) = self.match_info.rendered_template_paths.get(node) {
112 self.out.push_str(&mod_path.display(self.db, self.edition).to_string());
113 if let Some(path) = ast::Path::cast(node.clone())
116 && let Some(segment) = path.segment()
117 {
118 for node_or_token in segment.syntax().children_with_tokens() {
119 if node_or_token.kind() != SyntaxKind::NAME_REF {
120 self.render_node_or_token(&node_or_token);
121 }
122 }
123 }
124 } else {
125 self.render_node_children(node);
126 }
127 }
128
129 fn render_token(&mut self, token: &SyntaxToken) {
130 if let Some(placeholder) = self.rule.get_placeholder(token) {
131 if let Some(placeholder_value) =
132 self.match_info.placeholder_values.get(&placeholder.ident)
133 {
134 let range = &placeholder_value.range.range;
135 let mut matched_text =
136 self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
137 if !token_is_method_call_receiver(token)
143 && (placeholder_value.autoderef_count > 0
144 || placeholder_value.autoref_kind != ast::SelfParamKind::Owned)
145 {
146 cov_mark::hit!(replace_autoref_autoderef_capture);
147 let ref_kind = match placeholder_value.autoref_kind {
148 ast::SelfParamKind::Owned => "",
149 ast::SelfParamKind::Ref => "&",
150 ast::SelfParamKind::MutRef => "&mut ",
151 };
152 matched_text = format!(
153 "{}{}{}",
154 ref_kind,
155 "*".repeat(placeholder_value.autoderef_count),
156 matched_text
157 );
158 }
159 let edit = matches_to_edit_at_offset(
160 self.db,
161 &placeholder_value.inner_matches,
162 self.file_src,
163 range.start(),
164 self.rules,
165 );
166 let needs_parenthesis =
167 self.placeholder_tokens_requiring_parenthesis.contains(token);
168 edit.apply(&mut matched_text);
169 if needs_parenthesis {
170 self.out.push('(');
171 }
172 self.placeholder_tokens_by_range.insert(
173 TextRange::new(
174 TextSize::of(&self.out),
175 TextSize::of(&self.out) + TextSize::of(&matched_text),
176 ),
177 token.clone(),
178 );
179 self.out.push_str(&matched_text);
180 if needs_parenthesis {
181 self.out.push(')');
182 }
183 } else {
184 panic!(
187 "Internal error: replacement referenced unknown placeholder {}",
188 placeholder.ident
189 );
190 }
191 } else {
192 self.out.push_str(token.text());
193 }
194 }
195
196 fn maybe_rerender_with_extra_parenthesis(&mut self, template: &SyntaxNode) {
200 if let Some(node) = parse_as_kind(&self.out, template.kind()) {
201 self.remove_node_ranges(node);
202 if self.placeholder_tokens_by_range.is_empty() {
203 return;
204 }
205 self.placeholder_tokens_requiring_parenthesis =
206 self.placeholder_tokens_by_range.values().cloned().collect();
207 self.out.clear();
208 self.render_node(template);
209 }
210 }
211
212 fn remove_node_ranges(&mut self, node: SyntaxNode) {
213 self.placeholder_tokens_by_range.remove(&node.text_range());
214 for child in node.children() {
215 self.remove_node_ranges(child);
216 }
217 }
218}
219
220fn token_is_method_call_receiver(token: &SyntaxToken) -> bool {
224 if let Some(receiver) = token
227 .parent_ancestors()
228 .find_map(ast::MethodCallExpr::cast)
229 .and_then(|call| call.receiver())
230 {
231 let tokens = receiver.syntax().descendants_with_tokens().filter_map(|node_or_token| {
232 match node_or_token {
233 SyntaxElement::Token(t) => Some(t),
234 _ => None,
235 }
236 });
237 if let Some((only_token,)) = tokens.collect_tuple() {
238 return only_token == *token;
239 }
240 }
241 false
242}
243
244fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> {
245 if ast::Expr::can_cast(kind)
246 && let Ok(expr) = fragments::expr(code)
247 {
248 return Some(expr);
249 }
250 if ast::Item::can_cast(kind)
251 && let Ok(item) = fragments::item(code)
252 {
253 return Some(item);
254 }
255 None
256}