diff options
Diffstat (limited to 'crates/ra_ssr/src/lib.rs')
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 162 |
1 files changed, 157 insertions, 5 deletions
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index e148f4564..422e15ee6 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -9,10 +9,11 @@ mod replacing; | |||
9 | #[cfg(test)] | 9 | #[cfg(test)] |
10 | mod tests; | 10 | mod tests; |
11 | 11 | ||
12 | use crate::matching::Match; | 12 | pub use crate::matching::Match; |
13 | use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason}; | ||
13 | use hir::Semantics; | 14 | use hir::Semantics; |
14 | use ra_db::{FileId, FileRange}; | 15 | use ra_db::{FileId, FileRange}; |
15 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode}; | 16 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange}; |
16 | use ra_text_edit::TextEdit; | 17 | use ra_text_edit::TextEdit; |
17 | use rustc_hash::FxHashMap; | 18 | use rustc_hash::FxHashMap; |
18 | 19 | ||
@@ -26,7 +27,7 @@ pub struct SsrRule { | |||
26 | } | 27 | } |
27 | 28 | ||
28 | #[derive(Debug)] | 29 | #[derive(Debug)] |
29 | struct SsrPattern { | 30 | pub struct SsrPattern { |
30 | raw: parsing::RawSearchPattern, | 31 | raw: parsing::RawSearchPattern, |
31 | /// Placeholders keyed by the stand-in ident that we use in Rust source code. | 32 | /// Placeholders keyed by the stand-in ident that we use in Rust source code. |
32 | placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>, | 33 | placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>, |
@@ -45,7 +46,7 @@ pub struct SsrError(String); | |||
45 | 46 | ||
46 | #[derive(Debug, Default)] | 47 | #[derive(Debug, Default)] |
47 | pub struct SsrMatches { | 48 | pub struct SsrMatches { |
48 | matches: Vec<Match>, | 49 | pub matches: Vec<Match>, |
49 | } | 50 | } |
50 | 51 | ||
51 | /// Searches a crate for pattern matches and possibly replaces them with something else. | 52 | /// Searches a crate for pattern matches and possibly replaces them with something else. |
@@ -64,6 +65,12 @@ impl<'db> MatchFinder<'db> { | |||
64 | self.rules.push(rule); | 65 | self.rules.push(rule); |
65 | } | 66 | } |
66 | 67 | ||
68 | /// 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. | ||
70 | pub fn add_search_pattern(&mut self, pattern: SsrPattern) { | ||
71 | self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() }) | ||
72 | } | ||
73 | |||
67 | pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { | 74 | pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { |
68 | let matches = self.find_matches_in_file(file_id); | 75 | let matches = self.find_matches_in_file(file_id); |
69 | if matches.matches.is_empty() { | 76 | if matches.matches.is_empty() { |
@@ -74,7 +81,7 @@ impl<'db> MatchFinder<'db> { | |||
74 | } | 81 | } |
75 | } | 82 | } |
76 | 83 | ||
77 | fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches { | 84 | pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches { |
78 | let file = self.sema.parse(file_id); | 85 | let file = self.sema.parse(file_id); |
79 | let code = file.syntax(); | 86 | let code = file.syntax(); |
80 | let mut matches = SsrMatches::default(); | 87 | let mut matches = SsrMatches::default(); |
@@ -82,6 +89,32 @@ impl<'db> MatchFinder<'db> { | |||
82 | matches | 89 | matches |
83 | } | 90 | } |
84 | 91 | ||
92 | /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match | ||
93 | /// them, while recording reasons why they don't match. This API is useful for command | ||
94 | /// line-based debugging where providing a range is difficult. | ||
95 | pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> { | ||
96 | use ra_db::SourceDatabaseExt; | ||
97 | let file = self.sema.parse(file_id); | ||
98 | let mut res = Vec::new(); | ||
99 | let file_text = self.sema.db.file_text(file_id); | ||
100 | let mut remaining_text = file_text.as_str(); | ||
101 | let mut base = 0; | ||
102 | let len = snippet.len() as u32; | ||
103 | while let Some(offset) = remaining_text.find(snippet) { | ||
104 | let start = base + offset as u32; | ||
105 | let end = start + len; | ||
106 | self.output_debug_for_nodes_at_range( | ||
107 | file.syntax(), | ||
108 | FileRange { file_id, range: TextRange::new(start.into(), end.into()) }, | ||
109 | &None, | ||
110 | &mut res, | ||
111 | ); | ||
112 | remaining_text = &remaining_text[offset + snippet.len()..]; | ||
113 | base = end; | ||
114 | } | ||
115 | res | ||
116 | } | ||
117 | |||
85 | fn find_matches( | 118 | fn find_matches( |
86 | &self, | 119 | &self, |
87 | code: &SyntaxNode, | 120 | code: &SyntaxNode, |
@@ -128,6 +161,59 @@ impl<'db> MatchFinder<'db> { | |||
128 | self.find_matches(&child, restrict_range, matches_out); | 161 | self.find_matches(&child, restrict_range, matches_out); |
129 | } | 162 | } |
130 | } | 163 | } |
164 | |||
165 | fn output_debug_for_nodes_at_range( | ||
166 | &self, | ||
167 | node: &SyntaxNode, | ||
168 | range: FileRange, | ||
169 | restrict_range: &Option<FileRange>, | ||
170 | out: &mut Vec<MatchDebugInfo>, | ||
171 | ) { | ||
172 | for node in node.children() { | ||
173 | let node_range = self.sema.original_range(&node); | ||
174 | if node_range.file_id != range.file_id || !node_range.range.contains_range(range.range) | ||
175 | { | ||
176 | continue; | ||
177 | } | ||
178 | if node_range.range == range.range { | ||
179 | for rule in &self.rules { | ||
180 | let pattern = | ||
181 | rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone()); | ||
182 | out.push(MatchDebugInfo { | ||
183 | matched: matching::get_match(true, rule, &node, restrict_range, &self.sema) | ||
184 | .map_err(|e| MatchFailureReason { | ||
185 | reason: e.reason.unwrap_or_else(|| { | ||
186 | "Match failed, but no reason was given".to_owned() | ||
187 | }), | ||
188 | }), | ||
189 | pattern, | ||
190 | node: node.clone(), | ||
191 | }); | ||
192 | } | ||
193 | } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) { | ||
194 | if let Some(expanded) = self.sema.expand(¯o_call) { | ||
195 | if let Some(tt) = macro_call.token_tree() { | ||
196 | self.output_debug_for_nodes_at_range( | ||
197 | &expanded, | ||
198 | range, | ||
199 | &Some(self.sema.original_range(tt.syntax())), | ||
200 | out, | ||
201 | ); | ||
202 | } | ||
203 | } | ||
204 | } else { | ||
205 | self.output_debug_for_nodes_at_range(&node, range, restrict_range, out); | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | pub struct MatchDebugInfo { | ||
212 | node: SyntaxNode, | ||
213 | /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item, | ||
214 | /// etc. Will be absent if the pattern can't be parsed as that kind. | ||
215 | pattern: Result<SyntaxNode, MatchFailureReason>, | ||
216 | matched: Result<Match, MatchFailureReason>, | ||
131 | } | 217 | } |
132 | 218 | ||
133 | impl std::fmt::Display for SsrError { | 219 | impl std::fmt::Display for SsrError { |
@@ -136,4 +222,70 @@ impl std::fmt::Display for SsrError { | |||
136 | } | 222 | } |
137 | } | 223 | } |
138 | 224 | ||
225 | impl std::fmt::Debug for MatchDebugInfo { | ||
226 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
227 | write!(f, "========= PATTERN ==========\n")?; | ||
228 | match &self.pattern { | ||
229 | Ok(pattern) => { | ||
230 | write!(f, "{:#?}", pattern)?; | ||
231 | } | ||
232 | Err(err) => { | ||
233 | write!(f, "{}", err.reason)?; | ||
234 | } | ||
235 | } | ||
236 | write!( | ||
237 | f, | ||
238 | "\n============ AST ===========\n\ | ||
239 | {:#?}\n============================\n", | ||
240 | self.node | ||
241 | )?; | ||
242 | match &self.matched { | ||
243 | Ok(_) => write!(f, "Node matched")?, | ||
244 | Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?, | ||
245 | } | ||
246 | Ok(()) | ||
247 | } | ||
248 | } | ||
249 | |||
250 | impl SsrPattern { | ||
251 | fn tree_for_kind_with_reason( | ||
252 | &self, | ||
253 | kind: SyntaxKind, | ||
254 | ) -> Result<&SyntaxNode, MatchFailureReason> { | ||
255 | record_match_fails_reasons_scope(true, || self.tree_for_kind(kind)) | ||
256 | .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() }) | ||
257 | } | ||
258 | } | ||
259 | |||
260 | impl SsrMatches { | ||
261 | /// Returns `self` with any nested matches removed and made into top-level matches. | ||
262 | pub fn flattened(self) -> SsrMatches { | ||
263 | let mut out = SsrMatches::default(); | ||
264 | self.flatten_into(&mut out); | ||
265 | out | ||
266 | } | ||
267 | |||
268 | fn flatten_into(self, out: &mut SsrMatches) { | ||
269 | for mut m in self.matches { | ||
270 | for p in m.placeholder_values.values_mut() { | ||
271 | std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out); | ||
272 | } | ||
273 | out.matches.push(m); | ||
274 | } | ||
275 | } | ||
276 | } | ||
277 | |||
278 | impl Match { | ||
279 | pub fn matched_text(&self) -> String { | ||
280 | self.matched_node.text().to_string() | ||
281 | } | ||
282 | } | ||
283 | |||
139 | impl std::error::Error for SsrError {} | 284 | impl std::error::Error for SsrError {} |
285 | |||
286 | #[cfg(test)] | ||
287 | impl MatchDebugInfo { | ||
288 | pub(crate) fn match_failure_reason(&self) -> Option<&str> { | ||
289 | self.matched.as_ref().err().map(|r| r.reason.as_str()) | ||
290 | } | ||
291 | } | ||