diff options
Diffstat (limited to 'crates/ra_ssr/src')
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 76 | ||||
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 42 | ||||
-rw-r--r-- | crates/ra_ssr/src/parsing.rs | 106 | ||||
-rw-r--r-- | crates/ra_ssr/src/replacing.rs | 6 |
4 files changed, 123 insertions, 107 deletions
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index cca4576ce..3009dcb93 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -13,35 +13,27 @@ mod tests; | |||
13 | 13 | ||
14 | pub use crate::errors::SsrError; | 14 | pub use crate::errors::SsrError; |
15 | pub use crate::matching::Match; | 15 | pub use crate::matching::Match; |
16 | use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason}; | 16 | use crate::matching::MatchFailureReason; |
17 | use hir::Semantics; | 17 | use hir::Semantics; |
18 | use parsing::SsrTemplate; | ||
18 | use ra_db::{FileId, FileRange}; | 19 | use ra_db::{FileId, FileRange}; |
19 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange}; | 20 | use ra_syntax::{ast, AstNode, SyntaxNode, TextRange}; |
20 | use ra_text_edit::TextEdit; | 21 | use ra_text_edit::TextEdit; |
21 | use rustc_hash::FxHashMap; | ||
22 | 22 | ||
23 | // A structured search replace rule. Create by calling `parse` on a str. | 23 | // A structured search replace rule. Create by calling `parse` on a str. |
24 | #[derive(Debug)] | 24 | #[derive(Debug)] |
25 | pub struct SsrRule { | 25 | pub struct SsrRule { |
26 | /// A structured pattern that we're searching for. | 26 | /// A structured pattern that we're searching for. |
27 | pattern: SsrPattern, | 27 | pattern: parsing::RawPattern, |
28 | /// What we'll replace it with. | 28 | /// What we'll replace it with. |
29 | template: parsing::SsrTemplate, | 29 | template: SsrTemplate, |
30 | parsed_rules: Vec<parsing::ParsedRule>, | ||
30 | } | 31 | } |
31 | 32 | ||
32 | #[derive(Debug)] | 33 | #[derive(Debug)] |
33 | pub struct SsrPattern { | 34 | pub struct SsrPattern { |
34 | raw: parsing::RawSearchPattern, | 35 | raw: parsing::RawPattern, |
35 | /// Placeholders keyed by the stand-in ident that we use in Rust source code. | 36 | parsed_rules: Vec<parsing::ParsedRule>, |
36 | placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>, | ||
37 | // We store our search pattern, parsed as each different kind of thing we can look for. As we | ||
38 | // traverse the AST, we get the appropriate one of these for the type of node we're on. For many | ||
39 | // search patterns, only some of these will be present. | ||
40 | expr: Option<SyntaxNode>, | ||
41 | type_ref: Option<SyntaxNode>, | ||
42 | item: Option<SyntaxNode>, | ||
43 | path: Option<SyntaxNode>, | ||
44 | pattern: Option<SyntaxNode>, | ||
45 | } | 37 | } |
46 | 38 | ||
47 | #[derive(Debug, Default)] | 39 | #[derive(Debug, Default)] |
@@ -53,7 +45,7 @@ pub struct SsrMatches { | |||
53 | pub struct MatchFinder<'db> { | 45 | pub struct MatchFinder<'db> { |
54 | /// Our source of information about the user's code. | 46 | /// Our source of information about the user's code. |
55 | sema: Semantics<'db, ra_ide_db::RootDatabase>, | 47 | sema: Semantics<'db, ra_ide_db::RootDatabase>, |
56 | rules: Vec<SsrRule>, | 48 | rules: Vec<parsing::ParsedRule>, |
57 | } | 49 | } |
58 | 50 | ||
59 | impl<'db> MatchFinder<'db> { | 51 | impl<'db> MatchFinder<'db> { |
@@ -61,14 +53,17 @@ impl<'db> MatchFinder<'db> { | |||
61 | MatchFinder { sema: Semantics::new(db), rules: Vec::new() } | 53 | MatchFinder { sema: Semantics::new(db), rules: Vec::new() } |
62 | } | 54 | } |
63 | 55 | ||
56 | /// Adds a rule to be applied. The order in which rules are added matters. Earlier rules take | ||
57 | /// precedence. If a node is matched by an earlier rule, then later rules won't be permitted to | ||
58 | /// match to it. | ||
64 | pub fn add_rule(&mut self, rule: SsrRule) { | 59 | pub fn add_rule(&mut self, rule: SsrRule) { |
65 | self.rules.push(rule); | 60 | self.add_parsed_rules(rule.parsed_rules); |
66 | } | 61 | } |
67 | 62 | ||
68 | /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you | 63 | /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you |
69 | /// intend to do replacement, use `add_rule` instead. | 64 | /// intend to do replacement, use `add_rule` instead. |
70 | pub fn add_search_pattern(&mut self, pattern: SsrPattern) { | 65 | pub fn add_search_pattern(&mut self, pattern: SsrPattern) { |
71 | self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() }) | 66 | self.add_parsed_rules(pattern.parsed_rules); |
72 | } | 67 | } |
73 | 68 | ||
74 | pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { | 69 | pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { |
@@ -115,6 +110,14 @@ impl<'db> MatchFinder<'db> { | |||
115 | res | 110 | res |
116 | } | 111 | } |
117 | 112 | ||
113 | fn add_parsed_rules(&mut self, parsed_rules: Vec<parsing::ParsedRule>) { | ||
114 | // FIXME: This doesn't need to be a for loop, but does in a subsequent commit. Justify it | ||
115 | // being a for-loop. | ||
116 | for parsed_rule in parsed_rules { | ||
117 | self.rules.push(parsed_rule); | ||
118 | } | ||
119 | } | ||
120 | |||
118 | fn find_matches( | 121 | fn find_matches( |
119 | &self, | 122 | &self, |
120 | code: &SyntaxNode, | 123 | code: &SyntaxNode, |
@@ -177,8 +180,13 @@ impl<'db> MatchFinder<'db> { | |||
177 | } | 180 | } |
178 | if node_range.range == range.range { | 181 | if node_range.range == range.range { |
179 | for rule in &self.rules { | 182 | for rule in &self.rules { |
180 | let pattern = | 183 | // For now we ignore rules that have a different kind than our node, otherwise |
181 | rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone()); | 184 | // we get lots of noise. If at some point we add support for restricting rules |
185 | // to a particular kind of thing (e.g. only match type references), then we can | ||
186 | // relax this. | ||
187 | if rule.pattern.kind() != node.kind() { | ||
188 | continue; | ||
189 | } | ||
182 | out.push(MatchDebugInfo { | 190 | out.push(MatchDebugInfo { |
183 | matched: matching::get_match(true, rule, &node, restrict_range, &self.sema) | 191 | matched: matching::get_match(true, rule, &node, restrict_range, &self.sema) |
184 | .map_err(|e| MatchFailureReason { | 192 | .map_err(|e| MatchFailureReason { |
@@ -186,7 +194,7 @@ impl<'db> MatchFinder<'db> { | |||
186 | "Match failed, but no reason was given".to_owned() | 194 | "Match failed, but no reason was given".to_owned() |
187 | }), | 195 | }), |
188 | }), | 196 | }), |
189 | pattern, | 197 | pattern: rule.pattern.clone(), |
190 | node: node.clone(), | 198 | node: node.clone(), |
191 | }); | 199 | }); |
192 | } | 200 | } |
@@ -209,9 +217,8 @@ impl<'db> MatchFinder<'db> { | |||
209 | 217 | ||
210 | pub struct MatchDebugInfo { | 218 | pub struct MatchDebugInfo { |
211 | node: SyntaxNode, | 219 | node: SyntaxNode, |
212 | /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item, | 220 | /// Our search pattern parsed as an expression or item, etc |
213 | /// etc. Will be absent if the pattern can't be parsed as that kind. | 221 | pattern: SyntaxNode, |
214 | pattern: Result<SyntaxNode, MatchFailureReason>, | ||
215 | matched: Result<Match, MatchFailureReason>, | 222 | matched: Result<Match, MatchFailureReason>, |
216 | } | 223 | } |
217 | 224 | ||
@@ -228,29 +235,12 @@ impl std::fmt::Debug for MatchDebugInfo { | |||
228 | self.node | 235 | self.node |
229 | )?; | 236 | )?; |
230 | writeln!(f, "========= PATTERN ==========")?; | 237 | writeln!(f, "========= PATTERN ==========")?; |
231 | match &self.pattern { | 238 | writeln!(f, "{:#?}", self.pattern)?; |
232 | Ok(pattern) => { | ||
233 | writeln!(f, "{:#?}", pattern)?; | ||
234 | } | ||
235 | Err(err) => { | ||
236 | writeln!(f, "{}", err.reason)?; | ||
237 | } | ||
238 | } | ||
239 | writeln!(f, "============================")?; | 239 | writeln!(f, "============================")?; |
240 | Ok(()) | 240 | Ok(()) |
241 | } | 241 | } |
242 | } | 242 | } |
243 | 243 | ||
244 | impl SsrPattern { | ||
245 | fn tree_for_kind_with_reason( | ||
246 | &self, | ||
247 | kind: SyntaxKind, | ||
248 | ) -> Result<&SyntaxNode, MatchFailureReason> { | ||
249 | record_match_fails_reasons_scope(true, || self.tree_for_kind(kind)) | ||
250 | .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() }) | ||
251 | } | ||
252 | } | ||
253 | |||
254 | impl SsrMatches { | 244 | impl SsrMatches { |
255 | /// Returns `self` with any nested matches removed and made into top-level matches. | 245 | /// Returns `self` with any nested matches removed and made into top-level matches. |
256 | pub fn flattened(self) -> SsrMatches { | 246 | pub fn flattened(self) -> SsrMatches { |
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index 50b29eab2..842f4b6f3 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs | |||
@@ -2,8 +2,8 @@ | |||
2 | //! process of matching, placeholder values are recorded. | 2 | //! process of matching, placeholder values are recorded. |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{ |
5 | parsing::{Constraint, NodeKind, Placeholder, SsrTemplate}, | 5 | parsing::{Constraint, NodeKind, ParsedRule, Placeholder, SsrTemplate}, |
6 | SsrMatches, SsrPattern, SsrRule, | 6 | SsrMatches, |
7 | }; | 7 | }; |
8 | use hir::Semantics; | 8 | use hir::Semantics; |
9 | use ra_db::FileRange; | 9 | use ra_db::FileRange; |
@@ -50,7 +50,7 @@ pub struct Match { | |||
50 | pub(crate) ignored_comments: Vec<ast::Comment>, | 50 | pub(crate) ignored_comments: Vec<ast::Comment>, |
51 | // A copy of the template for the rule that produced this match. We store this on the match for | 51 | // A copy of the template for the rule that produced this match. We store this on the match for |
52 | // if/when we do replacement. | 52 | // if/when we do replacement. |
53 | pub(crate) template: SsrTemplate, | 53 | pub(crate) template: Option<SsrTemplate>, |
54 | } | 54 | } |
55 | 55 | ||
56 | /// Represents a `$var` in an SSR query. | 56 | /// Represents a `$var` in an SSR query. |
@@ -86,7 +86,7 @@ pub(crate) struct MatchFailed { | |||
86 | /// parent module, we don't populate nested matches. | 86 | /// parent module, we don't populate nested matches. |
87 | pub(crate) fn get_match( | 87 | pub(crate) fn get_match( |
88 | debug_active: bool, | 88 | debug_active: bool, |
89 | rule: &SsrRule, | 89 | rule: &ParsedRule, |
90 | code: &SyntaxNode, | 90 | code: &SyntaxNode, |
91 | restrict_range: &Option<FileRange>, | 91 | restrict_range: &Option<FileRange>, |
92 | sema: &Semantics<ra_ide_db::RootDatabase>, | 92 | sema: &Semantics<ra_ide_db::RootDatabase>, |
@@ -102,7 +102,7 @@ struct Matcher<'db, 'sema> { | |||
102 | /// If any placeholders come from anywhere outside of this range, then the match will be | 102 | /// If any placeholders come from anywhere outside of this range, then the match will be |
103 | /// rejected. | 103 | /// rejected. |
104 | restrict_range: Option<FileRange>, | 104 | restrict_range: Option<FileRange>, |
105 | rule: &'sema SsrRule, | 105 | rule: &'sema ParsedRule, |
106 | } | 106 | } |
107 | 107 | ||
108 | /// Which phase of matching we're currently performing. We do two phases because most attempted | 108 | /// Which phase of matching we're currently performing. We do two phases because most attempted |
@@ -117,15 +117,14 @@ enum Phase<'a> { | |||
117 | 117 | ||
118 | impl<'db, 'sema> Matcher<'db, 'sema> { | 118 | impl<'db, 'sema> Matcher<'db, 'sema> { |
119 | fn try_match( | 119 | fn try_match( |
120 | rule: &'sema SsrRule, | 120 | rule: &ParsedRule, |
121 | code: &SyntaxNode, | 121 | code: &SyntaxNode, |
122 | restrict_range: &Option<FileRange>, | 122 | restrict_range: &Option<FileRange>, |
123 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, | 123 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, |
124 | ) -> Result<Match, MatchFailed> { | 124 | ) -> Result<Match, MatchFailed> { |
125 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; | 125 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; |
126 | let pattern_tree = rule.pattern.tree_for_kind(code.kind())?; | ||
127 | // First pass at matching, where we check that node types and idents match. | 126 | // First pass at matching, where we check that node types and idents match. |
128 | match_state.attempt_match_node(&mut Phase::First, &pattern_tree, code)?; | 127 | match_state.attempt_match_node(&mut Phase::First, &rule.pattern, code)?; |
129 | match_state.validate_range(&sema.original_range(code))?; | 128 | match_state.validate_range(&sema.original_range(code))?; |
130 | let mut the_match = Match { | 129 | let mut the_match = Match { |
131 | range: sema.original_range(code), | 130 | range: sema.original_range(code), |
@@ -136,7 +135,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
136 | }; | 135 | }; |
137 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do | 136 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do |
138 | // any other more expensive checks that we didn't want to do on the first pass. | 137 | // any other more expensive checks that we didn't want to do on the first pass. |
139 | match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &pattern_tree, code)?; | 138 | match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &rule.pattern, code)?; |
140 | Ok(the_match) | 139 | Ok(the_match) |
141 | } | 140 | } |
142 | 141 | ||
@@ -444,8 +443,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
444 | } | 443 | } |
445 | 444 | ||
446 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | 445 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { |
447 | only_ident(element.clone()) | 446 | only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident)) |
448 | .and_then(|ident| self.rule.pattern.placeholders_by_stand_in.get(ident.text())) | ||
449 | } | 447 | } |
450 | } | 448 | } |
451 | 449 | ||
@@ -510,28 +508,6 @@ impl PlaceholderMatch { | |||
510 | } | 508 | } |
511 | } | 509 | } |
512 | 510 | ||
513 | impl SsrPattern { | ||
514 | pub(crate) fn tree_for_kind(&self, kind: SyntaxKind) -> Result<&SyntaxNode, MatchFailed> { | ||
515 | let (tree, kind_name) = if ast::Expr::can_cast(kind) { | ||
516 | (&self.expr, "expression") | ||
517 | } else if ast::TypeRef::can_cast(kind) { | ||
518 | (&self.type_ref, "type reference") | ||
519 | } else if ast::ModuleItem::can_cast(kind) { | ||
520 | (&self.item, "item") | ||
521 | } else if ast::Path::can_cast(kind) { | ||
522 | (&self.path, "path") | ||
523 | } else if ast::Pat::can_cast(kind) { | ||
524 | (&self.pattern, "pattern") | ||
525 | } else { | ||
526 | fail_match!("Matching nodes of kind {:?} is not supported", kind); | ||
527 | }; | ||
528 | match tree { | ||
529 | Some(tree) => Ok(tree), | ||
530 | None => fail_match!("Pattern cannot be parsed as a {}", kind_name), | ||
531 | } | ||
532 | } | ||
533 | } | ||
534 | |||
535 | impl NodeKind { | 511 | impl NodeKind { |
536 | fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { | 512 | fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { |
537 | let ok = match self { | 513 | let ok = match self { |
diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs index 4aee97bb2..682b7011a 100644 --- a/crates/ra_ssr/src/parsing.rs +++ b/crates/ra_ssr/src/parsing.rs | |||
@@ -7,17 +7,24 @@ | |||
7 | 7 | ||
8 | use crate::errors::bail; | 8 | use crate::errors::bail; |
9 | use crate::{SsrError, SsrPattern, SsrRule}; | 9 | use crate::{SsrError, SsrPattern, SsrRule}; |
10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; | 10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, T}; |
11 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
12 | use std::str::FromStr; | 12 | use std::str::FromStr; |
13 | 13 | ||
14 | #[derive(Debug)] | ||
15 | pub(crate) struct ParsedRule { | ||
16 | pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>, | ||
17 | pub(crate) pattern: SyntaxNode, | ||
18 | pub(crate) template: Option<SsrTemplate>, | ||
19 | } | ||
20 | |||
14 | #[derive(Clone, Debug)] | 21 | #[derive(Clone, Debug)] |
15 | pub(crate) struct SsrTemplate { | 22 | pub(crate) struct SsrTemplate { |
16 | pub(crate) tokens: Vec<PatternElement>, | 23 | pub(crate) tokens: Vec<PatternElement>, |
17 | } | 24 | } |
18 | 25 | ||
19 | #[derive(Debug)] | 26 | #[derive(Debug)] |
20 | pub(crate) struct RawSearchPattern { | 27 | pub(crate) struct RawPattern { |
21 | tokens: Vec<PatternElement>, | 28 | tokens: Vec<PatternElement>, |
22 | } | 29 | } |
23 | 30 | ||
@@ -54,6 +61,50 @@ pub(crate) struct Token { | |||
54 | pub(crate) text: SmolStr, | 61 | pub(crate) text: SmolStr, |
55 | } | 62 | } |
56 | 63 | ||
64 | impl ParsedRule { | ||
65 | fn new( | ||
66 | pattern: &RawPattern, | ||
67 | template: Option<&SsrTemplate>, | ||
68 | ) -> Result<Vec<ParsedRule>, SsrError> { | ||
69 | let raw_pattern = pattern.as_rust_code(); | ||
70 | let mut builder = RuleBuilder { | ||
71 | placeholders_by_stand_in: pattern.placeholders_by_stand_in(), | ||
72 | rules: Vec::new(), | ||
73 | }; | ||
74 | builder.try_add(ast::Expr::parse(&raw_pattern), template); | ||
75 | builder.try_add(ast::TypeRef::parse(&raw_pattern), template); | ||
76 | builder.try_add(ast::ModuleItem::parse(&raw_pattern), template); | ||
77 | builder.try_add(ast::Path::parse(&raw_pattern), template); | ||
78 | builder.try_add(ast::Pat::parse(&raw_pattern), template); | ||
79 | builder.build() | ||
80 | } | ||
81 | } | ||
82 | |||
83 | struct RuleBuilder { | ||
84 | placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>, | ||
85 | rules: Vec<ParsedRule>, | ||
86 | } | ||
87 | |||
88 | impl RuleBuilder { | ||
89 | fn try_add<T: AstNode>(&mut self, pattern: Result<T, ()>, template: Option<&SsrTemplate>) { | ||
90 | match pattern { | ||
91 | Ok(pattern) => self.rules.push(ParsedRule { | ||
92 | placeholders_by_stand_in: self.placeholders_by_stand_in.clone(), | ||
93 | pattern: pattern.syntax().clone(), | ||
94 | template: template.cloned(), | ||
95 | }), | ||
96 | _ => {} | ||
97 | } | ||
98 | } | ||
99 | |||
100 | fn build(self) -> Result<Vec<ParsedRule>, SsrError> { | ||
101 | if self.rules.is_empty() { | ||
102 | bail!("Pattern is not a valid Rust expression, type, item, path or pattern"); | ||
103 | } | ||
104 | Ok(self.rules) | ||
105 | } | ||
106 | } | ||
107 | |||
57 | impl FromStr for SsrRule { | 108 | impl FromStr for SsrRule { |
58 | type Err = SsrError; | 109 | type Err = SsrError; |
59 | 110 | ||
@@ -68,21 +119,24 @@ impl FromStr for SsrRule { | |||
68 | if it.next().is_some() { | 119 | if it.next().is_some() { |
69 | return Err(SsrError("More than one delimiter found".into())); | 120 | return Err(SsrError("More than one delimiter found".into())); |
70 | } | 121 | } |
71 | let rule = SsrRule { pattern: pattern.parse()?, template: template.parse()? }; | 122 | let raw_pattern = pattern.parse()?; |
123 | let raw_template = template.parse()?; | ||
124 | let parsed_rules = ParsedRule::new(&raw_pattern, Some(&raw_template))?; | ||
125 | let rule = SsrRule { pattern: raw_pattern, template: raw_template, parsed_rules }; | ||
72 | validate_rule(&rule)?; | 126 | validate_rule(&rule)?; |
73 | Ok(rule) | 127 | Ok(rule) |
74 | } | 128 | } |
75 | } | 129 | } |
76 | 130 | ||
77 | impl FromStr for RawSearchPattern { | 131 | impl FromStr for RawPattern { |
78 | type Err = SsrError; | 132 | type Err = SsrError; |
79 | 133 | ||
80 | fn from_str(pattern_str: &str) -> Result<RawSearchPattern, SsrError> { | 134 | fn from_str(pattern_str: &str) -> Result<RawPattern, SsrError> { |
81 | Ok(RawSearchPattern { tokens: parse_pattern(pattern_str)? }) | 135 | Ok(RawPattern { tokens: parse_pattern(pattern_str)? }) |
82 | } | 136 | } |
83 | } | 137 | } |
84 | 138 | ||
85 | impl RawSearchPattern { | 139 | impl RawPattern { |
86 | /// Returns this search pattern as Rust source code that we can feed to the Rust parser. | 140 | /// Returns this search pattern as Rust source code that we can feed to the Rust parser. |
87 | fn as_rust_code(&self) -> String { | 141 | fn as_rust_code(&self) -> String { |
88 | let mut res = String::new(); | 142 | let mut res = String::new(); |
@@ -95,7 +149,7 @@ impl RawSearchPattern { | |||
95 | res | 149 | res |
96 | } | 150 | } |
97 | 151 | ||
98 | fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> { | 152 | pub(crate) fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> { |
99 | let mut res = FxHashMap::default(); | 153 | let mut res = FxHashMap::default(); |
100 | for t in &self.tokens { | 154 | for t in &self.tokens { |
101 | if let PatternElement::Placeholder(placeholder) = t { | 155 | if let PatternElement::Placeholder(placeholder) = t { |
@@ -106,30 +160,22 @@ impl RawSearchPattern { | |||
106 | } | 160 | } |
107 | } | 161 | } |
108 | 162 | ||
163 | impl ParsedRule { | ||
164 | pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> { | ||
165 | if token.kind() != SyntaxKind::IDENT { | ||
166 | return None; | ||
167 | } | ||
168 | self.placeholders_by_stand_in.get(token.text()) | ||
169 | } | ||
170 | } | ||
171 | |||
109 | impl FromStr for SsrPattern { | 172 | impl FromStr for SsrPattern { |
110 | type Err = SsrError; | 173 | type Err = SsrError; |
111 | 174 | ||
112 | fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> { | 175 | fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> { |
113 | let raw: RawSearchPattern = pattern_str.parse()?; | 176 | let raw_pattern = pattern_str.parse()?; |
114 | let raw_str = raw.as_rust_code(); | 177 | let parsed_rules = ParsedRule::new(&raw_pattern, None)?; |
115 | let res = SsrPattern { | 178 | Ok(SsrPattern { raw: raw_pattern, parsed_rules }) |
116 | expr: ast::Expr::parse(&raw_str).ok().map(|n| n.syntax().clone()), | ||
117 | type_ref: ast::TypeRef::parse(&raw_str).ok().map(|n| n.syntax().clone()), | ||
118 | item: ast::ModuleItem::parse(&raw_str).ok().map(|n| n.syntax().clone()), | ||
119 | path: ast::Path::parse(&raw_str).ok().map(|n| n.syntax().clone()), | ||
120 | pattern: ast::Pat::parse(&raw_str).ok().map(|n| n.syntax().clone()), | ||
121 | placeholders_by_stand_in: raw.placeholders_by_stand_in(), | ||
122 | raw, | ||
123 | }; | ||
124 | if res.expr.is_none() | ||
125 | && res.type_ref.is_none() | ||
126 | && res.item.is_none() | ||
127 | && res.path.is_none() | ||
128 | && res.pattern.is_none() | ||
129 | { | ||
130 | bail!("Pattern is not a valid Rust expression, type, item, path or pattern"); | ||
131 | } | ||
132 | Ok(res) | ||
133 | } | 179 | } |
134 | } | 180 | } |
135 | 181 | ||
@@ -173,7 +219,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> { | |||
173 | /// pattern didn't define. | 219 | /// pattern didn't define. |
174 | fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { | 220 | fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { |
175 | let mut defined_placeholders = FxHashSet::default(); | 221 | let mut defined_placeholders = FxHashSet::default(); |
176 | for p in &rule.pattern.raw.tokens { | 222 | for p in &rule.pattern.tokens { |
177 | if let PatternElement::Placeholder(placeholder) = p { | 223 | if let PatternElement::Placeholder(placeholder) = p { |
178 | defined_placeholders.insert(&placeholder.ident); | 224 | defined_placeholders.insert(&placeholder.ident); |
179 | } | 225 | } |
@@ -316,7 +362,7 @@ mod tests { | |||
316 | } | 362 | } |
317 | let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); | 363 | let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); |
318 | assert_eq!( | 364 | assert_eq!( |
319 | result.pattern.raw.tokens, | 365 | result.pattern.tokens, |
320 | vec![ | 366 | vec![ |
321 | token(SyntaxKind::IDENT, "foo"), | 367 | token(SyntaxKind::IDENT, "foo"), |
322 | token(T!['('], "("), | 368 | token(T!['('], "("), |
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( | |||
31 | 31 | ||
32 | fn render_replace(match_info: &Match, file_src: &str) -> String { | 32 | fn render_replace(match_info: &Match, file_src: &str) -> String { |
33 | let mut out = String::new(); | 33 | let mut out = String::new(); |
34 | for r in &match_info.template.tokens { | 34 | let template = match_info |
35 | .template | ||
36 | .as_ref() | ||
37 | .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern"); | ||
38 | for r in &template.tokens { | ||
35 | match r { | 39 | match r { |
36 | PatternElement::Token(t) => out.push_str(t.text.as_str()), | 40 | PatternElement::Token(t) => out.push_str(t.text.as_str()), |
37 | PatternElement::Placeholder(p) => { | 41 | PatternElement::Placeholder(p) => { |