@@ -9,10 +9,11 @@ mod replacing;
99#[ cfg( test) ]
1010mod tests;
1111
12- use crate :: matching:: Match ;
12+ pub use crate :: matching:: Match ;
13+ use crate :: matching:: { record_match_fails_reasons_scope, MatchFailureReason } ;
1314use hir:: Semantics ;
1415use ra_db:: { FileId , FileRange } ;
15- use ra_syntax:: { ast, AstNode , SmolStr , SyntaxNode } ;
16+ use ra_syntax:: { ast, AstNode , SmolStr , SyntaxKind , SyntaxNode , TextRange } ;
1617use ra_text_edit:: TextEdit ;
1718use rustc_hash:: FxHashMap ;
1819
@@ -26,7 +27,7 @@ pub struct SsrRule {
2627}
2728
2829#[ derive( Debug ) ]
29- struct SsrPattern {
30+ pub struct SsrPattern {
3031 raw : parsing:: RawSearchPattern ,
3132 /// Placeholders keyed by the stand-in ident that we use in Rust source code.
3233 placeholders_by_stand_in : FxHashMap < SmolStr , parsing:: Placeholder > ,
@@ -45,7 +46,7 @@ pub struct SsrError(String);
4546
4647#[ derive( Debug , Default ) ]
4748pub struct SsrMatches {
48- matches : Vec < Match > ,
49+ pub matches : Vec < Match > ,
4950}
5051
5152/// Searches a crate for pattern matches and possibly replaces them with something else.
@@ -64,6 +65,12 @@ impl<'db> MatchFinder<'db> {
6465 self . rules . push ( rule) ;
6566 }
6667
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+
6774 pub fn edits_for_file ( & self , file_id : FileId ) -> Option < TextEdit > {
6875 let matches = self . find_matches_in_file ( file_id) ;
6976 if matches. matches . is_empty ( ) {
@@ -74,14 +81,40 @@ impl<'db> MatchFinder<'db> {
7481 }
7582 }
7683
77- fn find_matches_in_file ( & self , file_id : FileId ) -> SsrMatches {
84+ pub fn find_matches_in_file ( & self , file_id : FileId ) -> SsrMatches {
7885 let file = self . sema . parse ( file_id) ;
7986 let code = file. syntax ( ) ;
8087 let mut matches = SsrMatches :: default ( ) ;
8188 self . find_matches ( code, & None , & mut matches) ;
8289 matches
8390 }
8491
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+
85118 fn find_matches (
86119 & self ,
87120 code : & SyntaxNode ,
@@ -128,6 +161,59 @@ impl<'db> MatchFinder<'db> {
128161 self . find_matches ( & child, restrict_range, matches_out) ;
129162 }
130163 }
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 ( & macro_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 > ,
131217}
132218
133219impl std:: fmt:: Display for SsrError {
@@ -136,4 +222,70 @@ impl std::fmt::Display for SsrError {
136222 }
137223}
138224
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+
139284impl 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+ }
0 commit comments