diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-06-22 12:50:34 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-22 12:50:34 +0100 |
commit | d144d69d2eded43a59c8edb59419b1b9e85c10a5 (patch) | |
tree | 0d52bdbb15723d25b7d3fff9ad25274c72e43434 /crates/ra_ssr/src/lib.rs | |
parent | 19701b39ac232b023ff9ab077a33c743df96d178 (diff) | |
parent | 662ab2ecc8e29eb5995b3c162fac869838bea9a2 (diff) |
Merge #4921
4921: Allow SSR to match type references, items, paths and patterns r=davidlattimore a=davidlattimore
Part of #3186
Co-authored-by: David Lattimore <[email protected]>
Diffstat (limited to 'crates/ra_ssr/src/lib.rs')
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 120 |
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 | |||
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 | // 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 | |||
114 | impl 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 | |||
120 | impl std::error::Error for SsrError {} | ||