aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/tests.rs')
-rw-r--r--crates/ra_ssr/src/tests.rs212
1 files changed, 62 insertions, 150 deletions
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index 8be60c293..9568d4432 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -1,150 +1,6 @@
1use crate::matching::MatchFailureReason; 1use crate::{MatchFinder, SsrRule};
2use crate::{matching, Match, MatchFinder, SsrMatches, SsrPattern, SsrRule}; 2use ra_db::{FileId, SourceDatabaseExt};
3use matching::record_match_fails_reasons_scope; 3use test_utils::mark;
4use ra_db::{FileId, FileRange, SourceDatabaseExt};
5use ra_syntax::ast::AstNode;
6use ra_syntax::{ast, SyntaxKind, SyntaxNode, TextRange};
7
8struct MatchDebugInfo {
9 node: SyntaxNode,
10 /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item,
11 /// etc. Will be absent if the pattern can't be parsed as that kind.
12 pattern: Result<SyntaxNode, MatchFailureReason>,
13 matched: Result<Match, MatchFailureReason>,
14}
15
16impl SsrPattern {
17 pub(crate) fn tree_for_kind_with_reason(
18 &self,
19 kind: SyntaxKind,
20 ) -> Result<&SyntaxNode, MatchFailureReason> {
21 record_match_fails_reasons_scope(true, || self.tree_for_kind(kind))
22 .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() })
23 }
24}
25
26impl std::fmt::Debug for MatchDebugInfo {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(f, "========= PATTERN ==========\n")?;
29 match &self.pattern {
30 Ok(pattern) => {
31 write!(f, "{:#?}", pattern)?;
32 }
33 Err(err) => {
34 write!(f, "{}", err.reason)?;
35 }
36 }
37 write!(
38 f,
39 "\n============ AST ===========\n\
40 {:#?}\n============================",
41 self.node
42 )?;
43 match &self.matched {
44 Ok(_) => write!(f, "Node matched")?,
45 Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?,
46 }
47 Ok(())
48 }
49}
50
51impl SsrMatches {
52 /// Returns `self` with any nested matches removed and made into top-level matches.
53 pub(crate) fn flattened(self) -> SsrMatches {
54 let mut out = SsrMatches::default();
55 self.flatten_into(&mut out);
56 out
57 }
58
59 fn flatten_into(self, out: &mut SsrMatches) {
60 for mut m in self.matches {
61 for p in m.placeholder_values.values_mut() {
62 std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out);
63 }
64 out.matches.push(m);
65 }
66 }
67}
68
69impl Match {
70 pub(crate) fn matched_text(&self) -> String {
71 self.matched_node.text().to_string()
72 }
73}
74
75impl<'db> MatchFinder<'db> {
76 /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
77 /// intend to do replacement, use `add_rule` instead.
78 fn add_search_pattern(&mut self, pattern: SsrPattern) {
79 self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
80 }
81
82 /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
83 /// them, while recording reasons why they don't match. This API is useful for command
84 /// line-based debugging where providing a range is difficult.
85 fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
86 let file = self.sema.parse(file_id);
87 let mut res = Vec::new();
88 let file_text = self.sema.db.file_text(file_id);
89 let mut remaining_text = file_text.as_str();
90 let mut base = 0;
91 let len = snippet.len() as u32;
92 while let Some(offset) = remaining_text.find(snippet) {
93 let start = base + offset as u32;
94 let end = start + len;
95 self.output_debug_for_nodes_at_range(
96 file.syntax(),
97 TextRange::new(start.into(), end.into()),
98 &None,
99 &mut res,
100 );
101 remaining_text = &remaining_text[offset + snippet.len()..];
102 base = end;
103 }
104 res
105 }
106
107 fn output_debug_for_nodes_at_range(
108 &self,
109 node: &SyntaxNode,
110 range: TextRange,
111 restrict_range: &Option<FileRange>,
112 out: &mut Vec<MatchDebugInfo>,
113 ) {
114 for node in node.children() {
115 if !node.text_range().contains_range(range) {
116 continue;
117 }
118 if node.text_range() == range {
119 for rule in &self.rules {
120 let pattern =
121 rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
122 out.push(MatchDebugInfo {
123 matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
124 .map_err(|e| MatchFailureReason {
125 reason: e.reason.unwrap_or_else(|| {
126 "Match failed, but no reason was given".to_owned()
127 }),
128 }),
129 pattern,
130 node: node.clone(),
131 });
132 }
133 } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
134 if let Some(expanded) = self.sema.expand(&macro_call) {
135 if let Some(tt) = macro_call.token_tree() {
136 self.output_debug_for_nodes_at_range(
137 &expanded,
138 range,
139 &Some(self.sema.original_range(tt.syntax())),
140 out,
141 );
142 }
143 }
144 }
145 }
146 }
147}
148 4
149fn parse_error_text(query: &str) -> String { 5fn parse_error_text(query: &str) -> String {
150 format!("{}", query.parse::<SsrRule>().unwrap_err()) 6 format!("{}", query.parse::<SsrRule>().unwrap_err())
@@ -152,12 +8,12 @@ fn parse_error_text(query: &str) -> String {
152 8
153#[test] 9#[test]
154fn parser_empty_query() { 10fn parser_empty_query() {
155 assert_eq!(parse_error_text(""), "Parse error: Cannot find delemiter `==>>`"); 11 assert_eq!(parse_error_text(""), "Parse error: Cannot find delimiter `==>>`");
156} 12}
157 13
158#[test] 14#[test]
159fn parser_no_delimiter() { 15fn parser_no_delimiter() {
160 assert_eq!(parse_error_text("foo()"), "Parse error: Cannot find delemiter `==>>`"); 16 assert_eq!(parse_error_text("foo()"), "Parse error: Cannot find delimiter `==>>`");
161} 17}
162 18
163#[test] 19#[test]
@@ -227,7 +83,7 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) {
227 let mut after = db.file_text(file_id).to_string(); 83 let mut after = db.file_text(file_id).to_string();
228 edits.apply(&mut after); 84 edits.apply(&mut after);
229 // Likewise, we need to make sure that whatever transformations fixture parsing applies, 85 // Likewise, we need to make sure that whatever transformations fixture parsing applies,
230 // also get appplied to our expected result. 86 // also get applied to our expected result.
231 let result = normalize_code(result); 87 let result = normalize_code(result);
232 assert_eq!(after, result); 88 assert_eq!(after, result);
233 } else { 89 } else {
@@ -260,6 +116,19 @@ fn assert_no_match(pattern: &str, code: &str) {
260 assert_matches(pattern, code, &[]); 116 assert_matches(pattern, code, &[]);
261} 117}
262 118
119fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
120 let (db, file_id) = single_file(code);
121 let mut match_finder = MatchFinder::new(&db);
122 match_finder.add_search_pattern(pattern.parse().unwrap());
123 let mut reasons = Vec::new();
124 for d in match_finder.debug_where_text_equal(file_id, snippet) {
125 if let Some(reason) = d.match_failure_reason() {
126 reasons.push(reason.to_owned());
127 }
128 }
129 assert_eq!(reasons, vec![expected_reason]);
130}
131
263#[test] 132#[test]
264fn ssr_function_to_method() { 133fn ssr_function_to_method() {
265 assert_ssr_transform( 134 assert_ssr_transform(
@@ -434,6 +303,22 @@ fn match_pattern() {
434} 303}
435 304
436#[test] 305#[test]
306fn literal_constraint() {
307 mark::check!(literal_constraint);
308 let code = r#"
309 fn f1() {
310 let x1 = Some(42);
311 let x2 = Some("foo");
312 let x3 = Some(x1);
313 let x4 = Some(40 + 2);
314 let x5 = Some(true);
315 }
316 "#;
317 assert_matches("Some(${a:kind(literal)})", code, &["Some(42)", "Some(\"foo\")", "Some(true)"]);
318 assert_matches("Some(${a:not(kind(literal))})", code, &["Some(x1)", "Some(40 + 2)"]);
319}
320
321#[test]
437fn match_reordered_struct_instantiation() { 322fn match_reordered_struct_instantiation() {
438 assert_matches( 323 assert_matches(
439 "Foo {aa: 1, b: 2, ccc: 3}", 324 "Foo {aa: 1, b: 2, ccc: 3}",
@@ -623,3 +508,30 @@ fn preserves_whitespace_within_macro_expansion() {
623 fn f() {macro1!(4 - 3 - 1 * 2}"#, 508 fn f() {macro1!(4 - 3 - 1 * 2}"#,
624 ) 509 )
625} 510}
511
512#[test]
513fn match_failure_reasons() {
514 let code = r#"
515 macro_rules! foo {
516 ($a:expr) => {
517 1 + $a + 2
518 };
519 }
520 fn f1() {
521 bar(1, 2);
522 foo!(5 + 43.to_string() + 5);
523 }
524 "#;
525 assert_match_failure_reason(
526 "bar($a, 3)",
527 code,
528 "bar(1, 2)",
529 r#"Pattern wanted token '3' (INT_NUMBER), but code had token '2' (INT_NUMBER)"#,
530 );
531 assert_match_failure_reason(
532 "42.to_string()",
533 code,
534 "43.to_string()",
535 r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#,
536 );
537}