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.rs120
1 files changed, 120 insertions, 0 deletions
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
new file mode 100644
index 000000000..fc716ae82
--- /dev/null
+++ b/crates/ra_ssr/src/lib.rs
@@ -0,0 +1,120 @@
1//! Structural Search Replace
2//!
3//! Allows searching the AST for code that matches one or more patterns and then replacing that code
4//! based on a template.
5
6mod matching;
7mod parsing;
8mod replacing;
9#[cfg(test)]
10mod tests;
11
12use crate::matching::Match;
13use hir::Semantics;
14use ra_db::{FileId, FileRange};
15use ra_syntax::{AstNode, SmolStr, SyntaxNode};
16use ra_text_edit::TextEdit;
17use rustc_hash::FxHashMap;
18
19// A structured search replace rule. Create by calling `parse` on a str.
20#[derive(Debug)]
21pub struct SsrRule {
22 /// A structured pattern that we're searching for.
23 pattern: SsrPattern,
24 /// What we'll replace it with.
25 template: parsing::SsrTemplate,
26}
27
28#[derive(Debug)]
29struct SsrPattern {
30 raw: parsing::RawSearchPattern,
31 /// Placeholders keyed by the stand-in ident that we use in Rust source code.
32 placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
33 // We store our search pattern, parsed as each different kind of thing we can look for. As we
34 // traverse the AST, we get the appropriate one of these for the type of node we're on. For many
35 // search patterns, only some of these will be present.
36 expr: Option<SyntaxNode>,
37 type_ref: Option<SyntaxNode>,
38 item: Option<SyntaxNode>,
39 path: Option<SyntaxNode>,
40 pattern: Option<SyntaxNode>,
41}
42
43#[derive(Debug, PartialEq)]
44pub struct SsrError(String);
45
46#[derive(Debug, Default)]
47pub struct SsrMatches {
48 matches: Vec<Match>,
49}
50
51/// Searches a crate for pattern matches and possibly replaces them with something else.
52pub struct MatchFinder<'db> {
53 /// Our source of information about the user's code.
54 sema: Semantics<'db, ra_ide_db::RootDatabase>,
55 rules: Vec<SsrRule>,
56}
57
58impl<'db> MatchFinder<'db> {
59 pub fn new(db: &'db ra_ide_db::RootDatabase) -> MatchFinder<'db> {
60 MatchFinder { sema: Semantics::new(db), rules: Vec::new() }
61 }
62
63 pub fn add_rule(&mut self, rule: SsrRule) {
64 self.rules.push(rule);
65 }
66
67 pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> {
68 let matches = self.find_matches_in_file(file_id);
69 if matches.matches.is_empty() {
70 None
71 } else {
72 Some(replacing::matches_to_edit(&matches))
73 }
74 }
75
76 fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
77 let file = self.sema.parse(file_id);
78 let code = file.syntax();
79 let mut matches = SsrMatches::default();
80 self.find_matches(code, &None, &mut matches);
81 matches
82 }
83
84 fn find_matches(
85 &self,
86 code: &SyntaxNode,
87 restrict_range: &Option<FileRange>,
88 matches_out: &mut SsrMatches,
89 ) {
90 for rule in &self.rules {
91 if let Ok(mut m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) {
92 // Continue searching in each of our placeholders.
93 for placeholder_value in m.placeholder_values.values_mut() {
94 // Don't search our placeholder if it's the entire matched node, otherwise we'd
95 // find the same match over and over until we got a stack overflow.
96 if placeholder_value.node != *code {
97 self.find_matches(
98 &placeholder_value.node,
99 restrict_range,
100 &mut placeholder_value.inner_matches,
101 );
102 }
103 }
104 matches_out.matches.push(m);
105 return;
106 }
107 }
108 for child in code.children() {
109 self.find_matches(&child, restrict_range, matches_out);
110 }
111 }
112}
113
114impl std::fmt::Display for SsrError {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 write!(f, "Parse error: {}", self.0)
117 }
118}
119
120impl std::error::Error for SsrError {}