ide_ssr/
replacing.rs

1//! Code for applying replacement templates for matches that have previously been found.
2
3use 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
14/// Returns a text edit that will replace each match in `matches` with its corresponding replacement
15/// template. Placeholders in the template will have been substituted with whatever they matched to
16/// in the original code.
17pub(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    // Map from a range within `out` to a token in `template` that represents a placeholder. This is
51    // used to validate that the generated source code doesn't split any placeholder expansions (see
52    // below).
53    placeholder_tokens_by_range: FxHashMap<TextRange, SyntaxToken>,
54    // Which placeholder tokens need to be wrapped in parenthesis in order to ensure that when `out`
55    // is parsed, placeholders don't get split. e.g. if a template of `$a.to_string()` results in `1
56    // + 2.to_string()` then the placeholder value `1 + 2` was split and needs parenthesis.
57    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            // Emit everything except for the segment's name-ref, since we already effectively
114            // emitted that as part of `mod_path`.
115            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 a method call is performed directly on the placeholder, then autoderef and
138                // autoref will apply, so we can just substitute whatever the placeholder matched to
139                // directly. If we're not applying a method call, then we need to add explicitly
140                // deref and ref in order to match whatever was being done implicitly at the match
141                // site.
142                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                // We validated that all placeholder references were valid before we
185                // started, so this shouldn't happen.
186                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    // Checks if the resulting code, when parsed doesn't split any placeholders due to different
197    // order of operations between the search pattern and the replacement template. If any do, then
198    // we rerender the template and wrap the problematic placeholders with parenthesis.
199    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
220/// Returns whether token is the receiver of a method call. Note, being within the receiver of a
221/// method call doesn't count. e.g. if the token is `$a`, then `$a.foo()` will return true, while
222/// `($a + $b).foo()` or `x.foo($a)` will return false.
223fn token_is_method_call_receiver(token: &SyntaxToken) -> bool {
224    // Find the first method call among the ancestors of `token`, then check if the only token
225    // within the receiver is `token`.
226    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}