From 1fce8b6ba32bebba36d588d07781e9e578845728 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Fri, 3 Jul 2020 12:57:17 +1000 Subject: SSR: Change the way rules are stored internally. Previously we had: - Multiple rules - Each rule had its pattern parsed as an expression, path etc This meant that there were two levels at which there could be multiple rules. Now we just have multiple rules. If a pattern can parse as more than one kind of thing, then they get stored as multiple separate rules. We also now don't have separate fields for the different kinds of things that a pattern can parse as. This makes adding new kinds of things simpler. Previously, add_search_pattern would construct a rule with a dummy replacement. Now the replacement is an Option. This is slightly cleaner and also opens the way for parsing the replacement template as the same kind of thing as the search pattern. --- crates/ra_ssr/src/replacing.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'crates/ra_ssr/src/replacing.rs') diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs index e43cc5167..81f8634ba 100644 --- a/crates/ra_ssr/src/replacing.rs +++ b/crates/ra_ssr/src/replacing.rs @@ -31,7 +31,11 @@ fn matches_to_edit_at_offset( fn render_replace(match_info: &Match, file_src: &str) -> String { let mut out = String::new(); - for r in &match_info.template.tokens { + let template = match_info + .template + .as_ref() + .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern"); + for r in &template.tokens { match r { PatternElement::Token(t) => out.push_str(t.text.as_str()), PatternElement::Placeholder(p) => { -- cgit v1.2.3 From 113abbeefee671266d2d9bebdbd517eb8b802ef8 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 19:15:19 +1000 Subject: SSR: Parse template as Rust code. This is in preparation for a subsequent commit where we add special handling for paths in the template, allowing them to be qualified differently in different contexts. --- crates/ra_ssr/src/replacing.rs | 106 +++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 36 deletions(-) (limited to 'crates/ra_ssr/src/replacing.rs') diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs index 81f8634ba..f1c5bdf14 100644 --- a/crates/ra_ssr/src/replacing.rs +++ b/crates/ra_ssr/src/replacing.rs @@ -1,70 +1,104 @@ //! Code for applying replacement templates for matches that have previously been found. use crate::matching::Var; -use crate::parsing::PatternElement; -use crate::{Match, SsrMatches}; +use crate::{parsing::ParsedRule, Match, SsrMatches}; use ra_syntax::ast::AstToken; -use ra_syntax::TextSize; +use ra_syntax::{SyntaxElement, SyntaxNode, SyntaxToken, TextSize}; use ra_text_edit::TextEdit; /// Returns a text edit that will replace each match in `matches` with its corresponding replacement /// template. Placeholders in the template will have been substituted with whatever they matched to /// in the original code. -pub(crate) fn matches_to_edit(matches: &SsrMatches, file_src: &str) -> TextEdit { - matches_to_edit_at_offset(matches, file_src, 0.into()) +pub(crate) fn matches_to_edit( + matches: &SsrMatches, + file_src: &str, + rules: &[ParsedRule], +) -> TextEdit { + matches_to_edit_at_offset(matches, file_src, 0.into(), rules) } fn matches_to_edit_at_offset( matches: &SsrMatches, file_src: &str, relative_start: TextSize, + rules: &[ParsedRule], ) -> TextEdit { let mut edit_builder = ra_text_edit::TextEditBuilder::default(); for m in &matches.matches { edit_builder.replace( m.range.range.checked_sub(relative_start).unwrap(), - render_replace(m, file_src), + render_replace(m, file_src, rules), ); } edit_builder.finish() } -fn render_replace(match_info: &Match, file_src: &str) -> String { +struct ReplacementRenderer<'a> { + match_info: &'a Match, + file_src: &'a str, + rules: &'a [ParsedRule], + rule: &'a ParsedRule, +} + +fn render_replace(match_info: &Match, file_src: &str, rules: &[ParsedRule]) -> String { let mut out = String::new(); - let template = match_info + let rule = &rules[match_info.rule_index]; + let template = rule .template .as_ref() .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern"); - for r in &template.tokens { - match r { - PatternElement::Token(t) => out.push_str(t.text.as_str()), - PatternElement::Placeholder(p) => { - if let Some(placeholder_value) = - match_info.placeholder_values.get(&Var(p.ident.to_string())) - { - let range = &placeholder_value.range.range; - let mut matched_text = - file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); - let edit = matches_to_edit_at_offset( - &placeholder_value.inner_matches, - file_src, - range.start(), - ); - edit.apply(&mut matched_text); - out.push_str(&matched_text); - } else { - // We validated that all placeholder references were valid before we - // started, so this shouldn't happen. - panic!( - "Internal error: replacement referenced unknown placeholder {}", - p.ident - ); - } - } - } - } + let renderer = ReplacementRenderer { match_info, file_src, rules, rule }; + renderer.render_node_children(&template, &mut out); for comment in &match_info.ignored_comments { out.push_str(&comment.syntax().to_string()); } out } + +impl ReplacementRenderer<'_> { + fn render_node_children(&self, node: &SyntaxNode, out: &mut String) { + for node_or_token in node.children_with_tokens() { + self.render_node_or_token(&node_or_token, out); + } + } + + fn render_node_or_token(&self, node_or_token: &SyntaxElement, out: &mut String) { + match node_or_token { + SyntaxElement::Token(token) => { + self.render_token(&token, out); + } + SyntaxElement::Node(child_node) => { + self.render_node_children(&child_node, out); + } + } + } + + fn render_token(&self, token: &SyntaxToken, out: &mut String) { + if let Some(placeholder) = self.rule.get_placeholder(&token) { + if let Some(placeholder_value) = + self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string())) + { + let range = &placeholder_value.range.range; + let mut matched_text = + self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); + let edit = matches_to_edit_at_offset( + &placeholder_value.inner_matches, + self.file_src, + range.start(), + self.rules, + ); + edit.apply(&mut matched_text); + out.push_str(&matched_text); + } else { + // We validated that all placeholder references were valid before we + // started, so this shouldn't happen. + panic!( + "Internal error: replacement referenced unknown placeholder {}", + placeholder.ident + ); + } + } else { + out.push_str(token.text().as_str()); + } + } +} -- cgit v1.2.3 From 757f755c29e041fd319af466d7d0418f54cb090a Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 16:46:29 +1000 Subject: SSR: Match paths based on what they resolve to Also render template paths appropriately for their context. --- crates/ra_ssr/src/replacing.rs | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'crates/ra_ssr/src/replacing.rs') diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs index f1c5bdf14..4b3f5509c 100644 --- a/crates/ra_ssr/src/replacing.rs +++ b/crates/ra_ssr/src/replacing.rs @@ -1,9 +1,9 @@ //! Code for applying replacement templates for matches that have previously been found. use crate::matching::Var; -use crate::{parsing::ParsedRule, Match, SsrMatches}; -use ra_syntax::ast::AstToken; -use ra_syntax::{SyntaxElement, SyntaxNode, SyntaxToken, TextSize}; +use crate::{resolving::ResolvedRule, Match, SsrMatches}; +use ra_syntax::ast::{self, AstToken}; +use ra_syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextSize}; use ra_text_edit::TextEdit; /// Returns a text edit that will replace each match in `matches` with its corresponding replacement @@ -12,7 +12,7 @@ use ra_text_edit::TextEdit; pub(crate) fn matches_to_edit( matches: &SsrMatches, file_src: &str, - rules: &[ParsedRule], + rules: &[ResolvedRule], ) -> TextEdit { matches_to_edit_at_offset(matches, file_src, 0.into(), rules) } @@ -21,7 +21,7 @@ fn matches_to_edit_at_offset( matches: &SsrMatches, file_src: &str, relative_start: TextSize, - rules: &[ParsedRule], + rules: &[ResolvedRule], ) -> TextEdit { let mut edit_builder = ra_text_edit::TextEditBuilder::default(); for m in &matches.matches { @@ -36,11 +36,11 @@ fn matches_to_edit_at_offset( struct ReplacementRenderer<'a> { match_info: &'a Match, file_src: &'a str, - rules: &'a [ParsedRule], - rule: &'a ParsedRule, + rules: &'a [ResolvedRule], + rule: &'a ResolvedRule, } -fn render_replace(match_info: &Match, file_src: &str, rules: &[ParsedRule]) -> String { +fn render_replace(match_info: &Match, file_src: &str, rules: &[ResolvedRule]) -> String { let mut out = String::new(); let rule = &rules[match_info.rule_index]; let template = rule @@ -48,7 +48,7 @@ fn render_replace(match_info: &Match, file_src: &str, rules: &[ParsedRule]) -> S .as_ref() .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern"); let renderer = ReplacementRenderer { match_info, file_src, rules, rule }; - renderer.render_node_children(&template, &mut out); + renderer.render_node(&template.node, &mut out); for comment in &match_info.ignored_comments { out.push_str(&comment.syntax().to_string()); } @@ -68,11 +68,31 @@ impl ReplacementRenderer<'_> { self.render_token(&token, out); } SyntaxElement::Node(child_node) => { - self.render_node_children(&child_node, out); + self.render_node(&child_node, out); } } } + fn render_node(&self, node: &SyntaxNode, out: &mut String) { + use ra_syntax::ast::AstNode; + if let Some(mod_path) = self.match_info.rendered_template_paths.get(&node) { + out.push_str(&mod_path.to_string()); + // Emit everything except for the segment's name-ref, since we already effectively + // emitted that as part of `mod_path`. + if let Some(path) = ast::Path::cast(node.clone()) { + if let Some(segment) = path.segment() { + for node_or_token in segment.syntax().children_with_tokens() { + if node_or_token.kind() != SyntaxKind::NAME_REF { + self.render_node_or_token(&node_or_token, out); + } + } + } + } + } else { + self.render_node_children(&node, out); + } + } + fn render_token(&self, token: &SyntaxToken, out: &mut String) { if let Some(placeholder) = self.rule.get_placeholder(&token) { if let Some(placeholder_value) = -- cgit v1.2.3