diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-07-24 13:46:55 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-07-24 13:46:55 +0100 |
commit | c3defe2532ba6ffd12a13bcbc8fdeda037665efc (patch) | |
tree | 831bf4dd44ec83d927face4ba17e57dcdeab7fbe /crates/ra_ssr/src/replacing.rs | |
parent | 0e5095d3cac11d4b569c6e1594bd07937556c812 (diff) | |
parent | 58680cb08ea535e1fb567416fa3466a744a01b99 (diff) |
Merge #5518
5518: Use resolved paths in SSR rules r=matklad a=davidlattimore
The main user-visible changes are:
* SSR now matches paths based on whether they resolve to the same thing instead of whether they're written the same.
* So `foo()` won't match `foo()` if it's a different function `foo()`, but will match `bar::foo()` if it's the same `foo`.
* Paths in the replacement will now be rendered with appropriate qualification for their context.
* For example `foo::Bar` will render as just `Bar` inside the module `foo`, but might render as `baz::foo::Bar` from elsewhere.
* This means that all paths in the search pattern and replacement template must be able to be resolved.
* It now also matters where you invoke SSR from, since paths are resolved relative to wherever that is.
* Search now uses find-uses on paths to locate places to try matching. This means that when a path is present in the pattern, search will generally be pretty fast.
* Function calls can now match method calls again, but this time only if they resolve to the same function.
Co-authored-by: David Lattimore <[email protected]>
Diffstat (limited to 'crates/ra_ssr/src/replacing.rs')
-rw-r--r-- | crates/ra_ssr/src/replacing.rs | 128 |
1 files changed, 93 insertions, 35 deletions
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs index e43cc5167..4b3f5509c 100644 --- a/crates/ra_ssr/src/replacing.rs +++ b/crates/ra_ssr/src/replacing.rs | |||
@@ -1,66 +1,124 @@ | |||
1 | //! Code for applying replacement templates for matches that have previously been found. | 1 | //! Code for applying replacement templates for matches that have previously been found. |
2 | 2 | ||
3 | use crate::matching::Var; | 3 | use crate::matching::Var; |
4 | use crate::parsing::PatternElement; | 4 | use crate::{resolving::ResolvedRule, Match, SsrMatches}; |
5 | use crate::{Match, SsrMatches}; | 5 | use ra_syntax::ast::{self, AstToken}; |
6 | use ra_syntax::ast::AstToken; | 6 | use ra_syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextSize}; |
7 | use ra_syntax::TextSize; | ||
8 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
9 | 8 | ||
10 | /// Returns a text edit that will replace each match in `matches` with its corresponding replacement | 9 | /// Returns a text edit that will replace each match in `matches` with its corresponding replacement |
11 | /// template. Placeholders in the template will have been substituted with whatever they matched to | 10 | /// template. Placeholders in the template will have been substituted with whatever they matched to |
12 | /// in the original code. | 11 | /// in the original code. |
13 | pub(crate) fn matches_to_edit(matches: &SsrMatches, file_src: &str) -> TextEdit { | 12 | pub(crate) fn matches_to_edit( |
14 | matches_to_edit_at_offset(matches, file_src, 0.into()) | 13 | matches: &SsrMatches, |
14 | file_src: &str, | ||
15 | rules: &[ResolvedRule], | ||
16 | ) -> TextEdit { | ||
17 | matches_to_edit_at_offset(matches, file_src, 0.into(), rules) | ||
15 | } | 18 | } |
16 | 19 | ||
17 | fn matches_to_edit_at_offset( | 20 | fn matches_to_edit_at_offset( |
18 | matches: &SsrMatches, | 21 | matches: &SsrMatches, |
19 | file_src: &str, | 22 | file_src: &str, |
20 | relative_start: TextSize, | 23 | relative_start: TextSize, |
24 | rules: &[ResolvedRule], | ||
21 | ) -> TextEdit { | 25 | ) -> TextEdit { |
22 | let mut edit_builder = ra_text_edit::TextEditBuilder::default(); | 26 | let mut edit_builder = ra_text_edit::TextEditBuilder::default(); |
23 | for m in &matches.matches { | 27 | for m in &matches.matches { |
24 | edit_builder.replace( | 28 | edit_builder.replace( |
25 | m.range.range.checked_sub(relative_start).unwrap(), | 29 | m.range.range.checked_sub(relative_start).unwrap(), |
26 | render_replace(m, file_src), | 30 | render_replace(m, file_src, rules), |
27 | ); | 31 | ); |
28 | } | 32 | } |
29 | edit_builder.finish() | 33 | edit_builder.finish() |
30 | } | 34 | } |
31 | 35 | ||
32 | fn render_replace(match_info: &Match, file_src: &str) -> String { | 36 | struct ReplacementRenderer<'a> { |
37 | match_info: &'a Match, | ||
38 | file_src: &'a str, | ||
39 | rules: &'a [ResolvedRule], | ||
40 | rule: &'a ResolvedRule, | ||
41 | } | ||
42 | |||
43 | fn render_replace(match_info: &Match, file_src: &str, rules: &[ResolvedRule]) -> String { | ||
33 | let mut out = String::new(); | 44 | let mut out = String::new(); |
34 | for r in &match_info.template.tokens { | 45 | let rule = &rules[match_info.rule_index]; |
35 | match r { | 46 | let template = rule |
36 | PatternElement::Token(t) => out.push_str(t.text.as_str()), | 47 | .template |
37 | PatternElement::Placeholder(p) => { | 48 | .as_ref() |
38 | if let Some(placeholder_value) = | 49 | .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern"); |
39 | match_info.placeholder_values.get(&Var(p.ident.to_string())) | 50 | let renderer = ReplacementRenderer { match_info, file_src, rules, rule }; |
40 | { | 51 | renderer.render_node(&template.node, &mut out); |
41 | let range = &placeholder_value.range.range; | 52 | for comment in &match_info.ignored_comments { |
42 | let mut matched_text = | 53 | out.push_str(&comment.syntax().to_string()); |
43 | file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); | 54 | } |
44 | let edit = matches_to_edit_at_offset( | 55 | out |
45 | &placeholder_value.inner_matches, | 56 | } |
46 | file_src, | 57 | |
47 | range.start(), | 58 | impl ReplacementRenderer<'_> { |
48 | ); | 59 | fn render_node_children(&self, node: &SyntaxNode, out: &mut String) { |
49 | edit.apply(&mut matched_text); | 60 | for node_or_token in node.children_with_tokens() { |
50 | out.push_str(&matched_text); | 61 | self.render_node_or_token(&node_or_token, out); |
51 | } else { | 62 | } |
52 | // We validated that all placeholder references were valid before we | 63 | } |
53 | // started, so this shouldn't happen. | 64 | |
54 | panic!( | 65 | fn render_node_or_token(&self, node_or_token: &SyntaxElement, out: &mut String) { |
55 | "Internal error: replacement referenced unknown placeholder {}", | 66 | match node_or_token { |
56 | p.ident | 67 | SyntaxElement::Token(token) => { |
57 | ); | 68 | self.render_token(&token, out); |
69 | } | ||
70 | SyntaxElement::Node(child_node) => { | ||
71 | self.render_node(&child_node, out); | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | fn render_node(&self, node: &SyntaxNode, out: &mut String) { | ||
77 | use ra_syntax::ast::AstNode; | ||
78 | if let Some(mod_path) = self.match_info.rendered_template_paths.get(&node) { | ||
79 | out.push_str(&mod_path.to_string()); | ||
80 | // Emit everything except for the segment's name-ref, since we already effectively | ||
81 | // emitted that as part of `mod_path`. | ||
82 | if let Some(path) = ast::Path::cast(node.clone()) { | ||
83 | if let Some(segment) = path.segment() { | ||
84 | for node_or_token in segment.syntax().children_with_tokens() { | ||
85 | if node_or_token.kind() != SyntaxKind::NAME_REF { | ||
86 | self.render_node_or_token(&node_or_token, out); | ||
87 | } | ||
88 | } | ||
58 | } | 89 | } |
59 | } | 90 | } |
91 | } else { | ||
92 | self.render_node_children(&node, out); | ||
60 | } | 93 | } |
61 | } | 94 | } |
62 | for comment in &match_info.ignored_comments { | 95 | |
63 | out.push_str(&comment.syntax().to_string()); | 96 | fn render_token(&self, token: &SyntaxToken, out: &mut String) { |
97 | if let Some(placeholder) = self.rule.get_placeholder(&token) { | ||
98 | if let Some(placeholder_value) = | ||
99 | self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string())) | ||
100 | { | ||
101 | let range = &placeholder_value.range.range; | ||
102 | let mut matched_text = | ||
103 | self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); | ||
104 | let edit = matches_to_edit_at_offset( | ||
105 | &placeholder_value.inner_matches, | ||
106 | self.file_src, | ||
107 | range.start(), | ||
108 | self.rules, | ||
109 | ); | ||
110 | edit.apply(&mut matched_text); | ||
111 | out.push_str(&matched_text); | ||
112 | } else { | ||
113 | // We validated that all placeholder references were valid before we | ||
114 | // started, so this shouldn't happen. | ||
115 | panic!( | ||
116 | "Internal error: replacement referenced unknown placeholder {}", | ||
117 | placeholder.ident | ||
118 | ); | ||
119 | } | ||
120 | } else { | ||
121 | out.push_str(token.text().as_str()); | ||
122 | } | ||
64 | } | 123 | } |
65 | out | ||
66 | } | 124 | } |