aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/lib.rs')
-rw-r--r--crates/ra_ssr/src/lib.rs76
1 files changed, 33 insertions, 43 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 {