diff options
Diffstat (limited to 'crates/ra_ssr/src/lib.rs')
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 122 |
1 files changed, 122 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..da26ee669 --- /dev/null +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -0,0 +1,122 @@ | |||
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 | |||
6 | mod matching; | ||
7 | mod parsing; | ||
8 | mod replacing; | ||
9 | #[cfg(test)] | ||
10 | mod tests; | ||
11 | |||
12 | use crate::matching::Match; | ||
13 | use hir::Semantics; | ||
14 | use ra_db::{FileId, FileRange}; | ||
15 | use ra_syntax::{AstNode, SmolStr, SyntaxNode}; | ||
16 | use ra_text_edit::TextEdit; | ||
17 | use rustc_hash::FxHashMap; | ||
18 | |||
19 | // A structured search replace rule. Create by calling `parse` on a str. | ||
20 | #[derive(Debug)] | ||
21 | pub 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)] | ||
29 | struct 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)] | ||
44 | pub struct SsrError(String); | ||
45 | |||
46 | #[derive(Debug, Default)] | ||
47 | pub struct SsrMatches { | ||
48 | matches: Vec<Match>, | ||
49 | } | ||
50 | |||
51 | /// Searches a crate for pattern matches and possibly replaces them with something else. | ||
52 | pub 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 | |||
58 | impl<'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 | if let Some(placeholder_node) = &placeholder_value.node { | ||
95 | // Don't search our placeholder if it's the entire matched node, otherwise we'd | ||
96 | // find the same match over and over until we got a stack overflow. | ||
97 | if placeholder_node != code { | ||
98 | self.find_matches( | ||
99 | placeholder_node, | ||
100 | restrict_range, | ||
101 | &mut placeholder_value.inner_matches, | ||
102 | ); | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | matches_out.matches.push(m); | ||
107 | return; | ||
108 | } | ||
109 | } | ||
110 | for child in code.children() { | ||
111 | self.find_matches(&child, restrict_range, matches_out); | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | |||
116 | impl std::fmt::Display for SsrError { | ||
117 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
118 | write!(f, "Parse error: {}", self.0) | ||
119 | } | ||
120 | } | ||
121 | |||
122 | impl std::error::Error for SsrError {} | ||