aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorDavid Lattimore <[email protected]>2020-07-03 03:57:17 +0100
committerDavid Lattimore <[email protected]>2020-07-24 12:34:00 +0100
commit1fce8b6ba32bebba36d588d07781e9e578845728 (patch)
treec027096421e49a7e0279711e0ccd7491ca3d6a9a /crates
parent2b53639e381b1f17c829fb33f6e4135a9c930f41 (diff)
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.
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ssr/src/lib.rs76
-rw-r--r--crates/ra_ssr/src/matching.rs42
-rw-r--r--crates/ra_ssr/src/parsing.rs106
-rw-r--r--crates/ra_ssr/src/replacing.rs6
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
14pub use crate::errors::SsrError; 14pub use crate::errors::SsrError;
15pub use crate::matching::Match; 15pub use crate::matching::Match;
16use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason}; 16use crate::matching::MatchFailureReason;
17use hir::Semantics; 17use hir::Semantics;
18use parsing::SsrTemplate;
18use ra_db::{FileId, FileRange}; 19use ra_db::{FileId, FileRange};
19use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange}; 20use ra_syntax::{ast, AstNode, SyntaxNode, TextRange};
20use ra_text_edit::TextEdit; 21use ra_text_edit::TextEdit;
21use 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)]
25pub struct SsrRule { 25pub 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)]
33pub struct SsrPattern { 34pub 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 {
53pub struct MatchFinder<'db> { 45pub 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
59impl<'db> MatchFinder<'db> { 51impl<'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
210pub struct MatchDebugInfo { 218pub 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
244impl 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
254impl SsrMatches { 244impl 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
4use crate::{ 4use crate::{
5 parsing::{Constraint, NodeKind, Placeholder, SsrTemplate}, 5 parsing::{Constraint, NodeKind, ParsedRule, Placeholder, SsrTemplate},
6 SsrMatches, SsrPattern, SsrRule, 6 SsrMatches,
7}; 7};
8use hir::Semantics; 8use hir::Semantics;
9use ra_db::FileRange; 9use 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.
87pub(crate) fn get_match( 87pub(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
118impl<'db, 'sema> Matcher<'db, 'sema> { 118impl<'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
513impl 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
535impl NodeKind { 511impl 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
8use crate::errors::bail; 8use crate::errors::bail;
9use crate::{SsrError, SsrPattern, SsrRule}; 9use crate::{SsrError, SsrPattern, SsrRule};
10use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; 10use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, T};
11use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
12use std::str::FromStr; 12use std::str::FromStr;
13 13
14#[derive(Debug)]
15pub(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)]
15pub(crate) struct SsrTemplate { 22pub(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)]
20pub(crate) struct RawSearchPattern { 27pub(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
64impl 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
83struct RuleBuilder {
84 placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
85 rules: Vec<ParsedRule>,
86}
87
88impl 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
57impl FromStr for SsrRule { 108impl 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
77impl FromStr for RawSearchPattern { 131impl 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
85impl RawSearchPattern { 139impl 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
163impl 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
109impl FromStr for SsrPattern { 172impl 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.
174fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { 220fn 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
32fn render_replace(match_info: &Match, file_src: &str) -> String { 32fn 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) => {