From 2b53639e381b1f17c829fb33f6e4135a9c930f41 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Tue, 21 Jul 2020 21:32:09 +1000 Subject: SSR: Use expect! in tests --- crates/ra_ssr/src/tests.rs | 71 ++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 37 deletions(-) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index f20ae2cdf..9f5306592 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -1,4 +1,5 @@ use crate::{MatchFinder, SsrRule}; +use expect::{expect, Expect}; use ra_db::{FileId, SourceDatabaseExt}; use test_utils::mark; @@ -61,16 +62,11 @@ fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { ra_ide_db::RootDatabase::with_single_file(code) } -fn assert_ssr_transform(rule: &str, input: &str, result: &str) { - assert_ssr_transforms(&[rule], input, result); +fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { + assert_ssr_transforms(&[rule], input, expected); } -fn normalize_code(code: &str) -> String { - let (db, file_id) = single_file(code); - db.file_text(file_id).to_string() -} - -fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { +fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { let (db, file_id) = single_file(input); let mut match_finder = MatchFinder::new(&db); for rule in rules { @@ -80,12 +76,9 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { if let Some(edits) = match_finder.edits_for_file(file_id) { // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters // stuff. - let mut after = db.file_text(file_id).to_string(); - edits.apply(&mut after); - // Likewise, we need to make sure that whatever transformations fixture parsing applies, - // also get applied to our expected result. - let result = normalize_code(result); - assert_eq!(after, result); + let mut actual = db.file_text(file_id).to_string(); + edits.apply(&mut actual); + expected.assert_eq(&actual); } else { panic!("No edits were made"); } @@ -149,7 +142,7 @@ fn ssr_function_to_method() { assert_ssr_transform( "my_function($a, $b) ==>> ($a).my_method($b)", "fn my_function() {} fn main() { loop { my_function( other_func(x, y), z + w) } }", - "fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }", + expect![["fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }"]], ) } @@ -158,7 +151,7 @@ fn ssr_nested_function() { assert_ssr_transform( "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", "fn foo() {} fn main { foo (x + value.method(b), x+y-z, true && false) }", - "fn foo() {} fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", + expect![["fn foo() {} fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }"]], ) } @@ -167,7 +160,7 @@ fn ssr_expected_spacing() { assert_ssr_transform( "foo($x) + bar() ==>> bar($x)", "fn foo() {} fn bar() {} fn main() { foo(5) + bar() }", - "fn foo() {} fn bar() {} fn main() { bar(5) }", + expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]], ); } @@ -176,7 +169,7 @@ fn ssr_with_extra_space() { assert_ssr_transform( "foo($x ) + bar() ==>> bar($x)", "fn foo() {} fn bar() {} fn main() { foo( 5 ) +bar( ) }", - "fn foo() {} fn bar() {} fn main() { bar(5) }", + expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]], ); } @@ -185,7 +178,7 @@ fn ssr_keeps_nested_comment() { assert_ssr_transform( "foo($x) ==>> bar($x)", "fn foo() {} fn main() { foo(other(5 /* using 5 */)) }", - "fn foo() {} fn main() { bar(other(5 /* using 5 */)) }", + expect![["fn foo() {} fn main() { bar(other(5 /* using 5 */)) }"]], ) } @@ -194,7 +187,7 @@ fn ssr_keeps_comment() { assert_ssr_transform( "foo($x) ==>> bar($x)", "fn foo() {} fn main() { foo(5 /* using 5 */) }", - "fn foo() {} fn main() { bar(5)/* using 5 */ }", + expect![["fn foo() {} fn main() { bar(5)/* using 5 */ }"]], ) } @@ -203,7 +196,7 @@ fn ssr_struct_lit() { assert_ssr_transform( "foo{a: $a, b: $b} ==>> foo::new($a, $b)", "fn foo() {} fn main() { foo{b:2, a:1} }", - "fn foo() {} fn main() { foo::new(1, 2) }", + expect![["fn foo() {} fn main() { foo::new(1, 2) }"]], ) } @@ -417,7 +410,7 @@ fn replace_function_call() { assert_ssr_transform( "foo() ==>> bar()", "fn foo() {} fn f1() {foo(); foo();}", - "fn foo() {} fn f1() {bar(); bar();}", + expect![["fn foo() {} fn f1() {bar(); bar();}"]], ); } @@ -426,7 +419,7 @@ fn replace_function_call_with_placeholders() { assert_ssr_transform( "foo($a, $b) ==>> bar($b, $a)", "fn foo() {} fn f1() {foo(5, 42)}", - "fn foo() {} fn f1() {bar(42, 5)}", + expect![["fn foo() {} fn f1() {bar(42, 5)}"]], ); } @@ -435,7 +428,7 @@ fn replace_nested_function_calls() { assert_ssr_transform( "foo($a) ==>> bar($a)", "fn foo() {} fn f1() {foo(foo(42))}", - "fn foo() {} fn f1() {bar(bar(42))}", + expect![["fn foo() {} fn f1() {bar(bar(42))}"]], ); } @@ -444,7 +437,7 @@ fn replace_type() { assert_ssr_transform( "Result<(), $a> ==>> Option<$a>", "struct Result {} fn f1() -> Result<(), Vec> {foo()}", - "struct Result {} fn f1() -> Option> {foo()}", + expect![["struct Result {} fn f1() -> Option> {foo()}"]], ); } @@ -453,7 +446,7 @@ fn replace_struct_init() { assert_ssr_transform( "Foo {a: $a, b: $b} ==>> Foo::new($a, $b)", "struct Foo {} fn f1() {Foo{b: 1, a: 2}}", - "struct Foo {} fn f1() {Foo::new(2, 1)}", + expect![["struct Foo {} fn f1() {Foo::new(2, 1)}"]], ); } @@ -462,12 +455,12 @@ fn replace_macro_invocations() { assert_ssr_transform( "try!($a) ==>> $a?", "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(try!(foo()));}", - "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}", + expect![["macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}"]], ); assert_ssr_transform( "foo!($a($b)) ==>> foo($b, $a)", "macro_rules! foo {() => {}} fn f1() {foo!(abc(def() + 2));}", - "macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}", + expect![["macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}"]], ); } @@ -476,12 +469,12 @@ fn replace_binary_op() { assert_ssr_transform( "$a + $b ==>> $b + $a", "fn f() {2 * 3 + 4 * 5}", - "fn f() {4 * 5 + 2 * 3}", + expect![["fn f() {4 * 5 + 2 * 3}"]], ); assert_ssr_transform( "$a + $b ==>> $b + $a", "fn f() {1 + 2 + 3 + 4}", - "fn f() {4 + 3 + 2 + 1}", + expect![["fn f() {4 + 3 + 2 + 1}"]], ); } @@ -495,7 +488,7 @@ fn multiple_rules() { assert_ssr_transforms( &["$a + 1 ==>> add_one($a)", "$a + $b ==>> add($a, $b)"], "fn f() -> i32 {3 + 2 + 1}", - "fn f() -> i32 {add_one(add(3, 2))}", + expect![["fn f() -> i32 {add_one(add(3, 2))}"]], ) } @@ -527,12 +520,14 @@ fn replace_within_macro_expansion() { macro_rules! macro1 { ($a:expr) => {$a} } - fn f() {macro1!(5.x().foo().o2())}"#, - r#" + fn f() {macro1!(5.x().foo().o2())} + "#, + expect![[r#" macro_rules! macro1 { ($a:expr) => {$a} } - fn f() {macro1!(bar(5.x()).o2())}"#, + fn f() {macro1!(bar(5.x()).o2())} + "#]], ) } @@ -544,12 +539,14 @@ fn preserves_whitespace_within_macro_expansion() { macro_rules! macro1 { ($a:expr) => {$a} } - fn f() {macro1!(1 * 2 + 3 + 4}"#, - r#" + fn f() {macro1!(1 * 2 + 3 + 4} + "#, + expect![[r#" macro_rules! macro1 { ($a:expr) => {$a} } - fn f() {macro1!(4 - 3 - 1 * 2}"#, + fn f() {macro1!(4 - 3 - 1 * 2} + "#]], ) } -- cgit v1.2.3 From 113abbeefee671266d2d9bebdbd517eb8b802ef8 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 19:15:19 +1000 Subject: SSR: Parse template as Rust code. This is in preparation for a subsequent commit where we add special handling for paths in the template, allowing them to be qualified differently in different contexts. --- crates/ra_ssr/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 9f5306592..1b03b7f4b 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -37,7 +37,7 @@ fn parser_repeated_name() { fn parser_invalid_pattern() { assert_eq!( parse_error_text(" ==>> ()"), - "Parse error: Pattern is not a valid Rust expression, type, item, path or pattern" + "Parse error: Not a valid Rust expression, type, item, path or pattern" ); } @@ -45,7 +45,7 @@ fn parser_invalid_pattern() { fn parser_invalid_template() { assert_eq!( parse_error_text("() ==>> )"), - "Parse error: Replacement is not a valid Rust expression, type, item, path or pattern" + "Parse error: Not a valid Rust expression, type, item, path or pattern" ); } -- cgit v1.2.3 From a45682ed96f18f962ac403419b4d143d59ba5283 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 16:23:43 +1000 Subject: Move iteration over all files into the SSR crate The methods `edits_for_file` and `find_matches_in_file` are replaced with just `edits` and `matches`. This simplifies the API a bit, but more importantly it makes it possible in a subsequent commit for SSR to decide to not search all files. --- crates/ra_ssr/src/tests.rs | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 1b03b7f4b..c7c37af2f 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -1,6 +1,8 @@ use crate::{MatchFinder, SsrRule}; use expect::{expect, Expect}; -use ra_db::{FileId, SourceDatabaseExt}; +use ra_db::{salsa::Durability, FileId, SourceDatabaseExt}; +use rustc_hash::FxHashSet; +use std::sync::Arc; use test_utils::mark; fn parse_error_text(query: &str) -> String { @@ -57,9 +59,15 @@ fn parser_undefined_placeholder_in_replacement() { ); } -fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { +pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { use ra_db::fixture::WithFixture; - ra_ide_db::RootDatabase::with_single_file(code) + use ra_ide_db::symbol_index::SymbolsDatabase; + let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code); + let mut db = db; + let mut local_roots = FxHashSet::default(); + local_roots.insert(ra_db::fixture::WORKSPACE); + db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); + (db, file_id) } fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { @@ -73,15 +81,16 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { let rule: SsrRule = rule.parse().unwrap(); match_finder.add_rule(rule); } - if let Some(edits) = match_finder.edits_for_file(file_id) { - // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters - // stuff. - let mut actual = db.file_text(file_id).to_string(); - edits.apply(&mut actual); - expected.assert_eq(&actual); - } else { + let edits = match_finder.edits(); + if edits.is_empty() { panic!("No edits were made"); } + assert_eq!(edits[0].file_id, file_id); + // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters + // stuff. + let mut actual = db.file_text(file_id).to_string(); + edits[0].edit.apply(&mut actual); + expected.assert_eq(&actual); } fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) { @@ -100,13 +109,8 @@ fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { let (db, file_id) = single_file(code); let mut match_finder = MatchFinder::new(&db); match_finder.add_search_pattern(pattern.parse().unwrap()); - let matched_strings: Vec = match_finder - .find_matches_in_file(file_id) - .flattened() - .matches - .iter() - .map(|m| m.matched_text()) - .collect(); + let matched_strings: Vec = + match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect(); if matched_strings != expected && !expected.is_empty() { print_match_debug_info(&match_finder, file_id, &expected[0]); } @@ -117,7 +121,7 @@ fn assert_no_match(pattern: &str, code: &str) { let (db, file_id) = single_file(code); let mut match_finder = MatchFinder::new(&db); match_finder.add_search_pattern(pattern.parse().unwrap()); - let matches = match_finder.find_matches_in_file(file_id).flattened().matches; + let matches = match_finder.matches().flattened().matches; if !matches.is_empty() { print_match_debug_info(&match_finder, file_id, &matches[0].matched_text()); panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches); -- cgit v1.2.3 From 6fcaaa1201c650ce22b71160f6e9bf2288d10a1a Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 16:06:14 +1000 Subject: SSR tests: Define all paths needed for templates In a later commit, paths in templates will be resolved. This allows us to render the path with appropriate qualifiers for its context. Here we prepare for that change by updating existing tests where I'd previously not bothered to define the items that the template referred to. --- crates/ra_ssr/src/tests.rs | 102 +++++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 26 deletions(-) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index c7c37af2f..11512c8cc 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -154,8 +154,19 @@ fn ssr_function_to_method() { fn ssr_nested_function() { assert_ssr_transform( "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", - "fn foo() {} fn main { foo (x + value.method(b), x+y-z, true && false) }", - expect![["fn foo() {} fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }"]], + r#" + //- /lib.rs crate:foo + fn foo() {} + fn bar() {} + fn baz() {} + fn main { foo (x + value.method(b), x+y-z, true && false) } + "#, + expect![[r#" + fn foo() {} + fn bar() {} + fn baz() {} + fn main { bar(true && false, baz(x + value.method(b), x+y-z)) } + "#]], ) } @@ -181,8 +192,8 @@ fn ssr_with_extra_space() { fn ssr_keeps_nested_comment() { assert_ssr_transform( "foo($x) ==>> bar($x)", - "fn foo() {} fn main() { foo(other(5 /* using 5 */)) }", - expect![["fn foo() {} fn main() { bar(other(5 /* using 5 */)) }"]], + "fn foo() {} fn bar() {} fn main() { foo(other(5 /* using 5 */)) }", + expect![["fn foo() {} fn bar() {} fn main() { bar(other(5 /* using 5 */)) }"]], ) } @@ -190,17 +201,25 @@ fn ssr_keeps_nested_comment() { fn ssr_keeps_comment() { assert_ssr_transform( "foo($x) ==>> bar($x)", - "fn foo() {} fn main() { foo(5 /* using 5 */) }", - expect![["fn foo() {} fn main() { bar(5)/* using 5 */ }"]], + "fn foo() {} fn bar() {} fn main() { foo(5 /* using 5 */) }", + expect![["fn foo() {} fn bar() {} fn main() { bar(5)/* using 5 */ }"]], ) } #[test] fn ssr_struct_lit() { assert_ssr_transform( - "foo{a: $a, b: $b} ==>> foo::new($a, $b)", - "fn foo() {} fn main() { foo{b:2, a:1} }", - expect![["fn foo() {} fn main() { foo::new(1, 2) }"]], + "Foo{a: $a, b: $b} ==>> Foo::new($a, $b)", + r#" + struct Foo() {} + impl Foo { fn new() {} } + fn main() { Foo{b:2, a:1} } + "#, + expect![[r#" + struct Foo() {} + impl Foo { fn new() {} } + fn main() { Foo::new(1, 2) } + "#]], ) } @@ -312,7 +331,7 @@ fn match_struct_instantiation() { fn match_path() { let code = r#" mod foo { - fn bar() {} + pub fn bar() {} } fn f() {foo::bar(42)}"#; assert_matches("foo::bar", code, &["foo::bar"]); @@ -413,8 +432,8 @@ fn no_match_split_expression() { fn replace_function_call() { assert_ssr_transform( "foo() ==>> bar()", - "fn foo() {} fn f1() {foo(); foo();}", - expect![["fn foo() {} fn f1() {bar(); bar();}"]], + "fn foo() {} fn bar() {} fn f1() {foo(); foo();}", + expect![["fn foo() {} fn bar() {} fn f1() {bar(); bar();}"]], ); } @@ -422,8 +441,8 @@ fn replace_function_call() { fn replace_function_call_with_placeholders() { assert_ssr_transform( "foo($a, $b) ==>> bar($b, $a)", - "fn foo() {} fn f1() {foo(5, 42)}", - expect![["fn foo() {} fn f1() {bar(42, 5)}"]], + "fn foo() {} fn bar() {} fn f1() {foo(5, 42)}", + expect![["fn foo() {} fn bar() {} fn f1() {bar(42, 5)}"]], ); } @@ -431,26 +450,40 @@ fn replace_function_call_with_placeholders() { fn replace_nested_function_calls() { assert_ssr_transform( "foo($a) ==>> bar($a)", - "fn foo() {} fn f1() {foo(foo(42))}", - expect![["fn foo() {} fn f1() {bar(bar(42))}"]], + "fn foo() {} fn bar() {} fn f1() {foo(foo(42))}", + expect![["fn foo() {} fn bar() {} fn f1() {bar(bar(42))}"]], ); } #[test] -fn replace_type() { +fn replace_associated_function_call() { assert_ssr_transform( - "Result<(), $a> ==>> Option<$a>", - "struct Result {} fn f1() -> Result<(), Vec> {foo()}", - expect![["struct Result {} fn f1() -> Option> {foo()}"]], + "Foo::new() ==>> Bar::new()", + r#" + struct Foo {} + impl Foo { fn new() {} } + struct Bar {} + impl Bar { fn new() {} } + fn f1() {Foo::new();} + "#, + expect![[r#" + struct Foo {} + impl Foo { fn new() {} } + struct Bar {} + impl Bar { fn new() {} } + fn f1() {Bar::new();} + "#]], ); } #[test] -fn replace_struct_init() { +fn replace_type() { assert_ssr_transform( - "Foo {a: $a, b: $b} ==>> Foo::new($a, $b)", - "struct Foo {} fn f1() {Foo{b: 1, a: 2}}", - expect![["struct Foo {} fn f1() {Foo::new(2, 1)}"]], + "Result<(), $a> ==>> Option<$a>", + "struct Result {} struct Option {} fn f1() -> Result<(), Vec> {foo()}", + expect![[ + "struct Result {} struct Option {} fn f1() -> Option> {foo()}" + ]], ); } @@ -491,8 +524,23 @@ fn match_binary_op() { fn multiple_rules() { assert_ssr_transforms( &["$a + 1 ==>> add_one($a)", "$a + $b ==>> add($a, $b)"], - "fn f() -> i32 {3 + 2 + 1}", - expect![["fn f() -> i32 {add_one(add(3, 2))}"]], + "fn add() {} fn add_one() {} fn f() -> i32 {3 + 2 + 1}", + expect![["fn add() {} fn add_one() {} fn f() -> i32 {add_one(add(3, 2))}"]], + ) +} + +#[test] +fn multiple_rules_with_nested_matches() { + assert_ssr_transforms( + &["foo1($a) ==>> bar1($a)", "foo2($a) ==>> bar2($a)"], + r#" + fn foo1() {} fn foo2() {} fn bar1() {} fn bar2() {} + fn f() {foo1(foo2(foo1(foo2(foo1(42)))))} + "#, + expect![[r#" + fn foo1() {} fn foo2() {} fn bar1() {} fn bar2() {} + fn f() {bar1(bar2(bar1(bar2(bar1(42)))))} + "#]], ) } @@ -524,12 +572,14 @@ fn replace_within_macro_expansion() { macro_rules! macro1 { ($a:expr) => {$a} } + fn bar() {} fn f() {macro1!(5.x().foo().o2())} "#, expect![[r#" macro_rules! macro1 { ($a:expr) => {$a} } + fn bar() {} fn f() {macro1!(bar(5.x()).o2())} "#]], ) -- cgit v1.2.3 From 699619a65cf816b927fffa77b2b38f611d8460bc Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 16:00:57 +1000 Subject: SSR: Add a couple of tests for non-recursive search These tests already pass, however once we switch to non-recursive search, it'd be easy for these tests to not pass. --- crates/ra_ssr/src/tests.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 11512c8cc..523035b31 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -585,6 +585,27 @@ fn replace_within_macro_expansion() { ) } +#[test] +fn replace_outside_and_within_macro_expansion() { + assert_ssr_transform( + "foo($a) ==>> bar($a)", + r#" + fn foo() {} fn bar() {} + macro_rules! macro1 { + ($a:expr) => {$a} + } + fn f() {foo(foo(macro1!(foo(foo(42)))))} + "#, + expect![[r#" + fn foo() {} fn bar() {} + macro_rules! macro1 { + ($a:expr) => {$a} + } + fn f() {bar(bar(macro1!(bar(bar(42)))))} + "#]], + ) +} + #[test] fn preserves_whitespace_within_macro_expansion() { assert_ssr_transform( @@ -631,3 +652,15 @@ fn match_failure_reasons() { r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#, ); } + +#[test] +fn overlapping_possible_matches() { + // There are three possible matches here, however the middle one, `foo(foo(foo(42)))` shouldn't + // match because it overlaps with the outer match. The inner match is permitted since it's is + // contained entirely within the placeholder of the outer match. + assert_matches( + "foo(foo($a))", + "fn foo() {} fn main() {foo(foo(foo(foo(42))))}", + &["foo(foo(42))", "foo(foo(foo(foo(42))))"], + ); +} -- cgit v1.2.3 From 3975952601888d9f77e466c12e8e389748984b33 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 15:00:28 +1000 Subject: SSR: Pass current file position through to SSR code. In a subsequent commit, it will be used for resolving paths. --- crates/ra_ssr/src/tests.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 523035b31..63d527894 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -1,6 +1,6 @@ use crate::{MatchFinder, SsrRule}; use expect::{expect, Expect}; -use ra_db::{salsa::Durability, FileId, SourceDatabaseExt}; +use ra_db::{salsa::Durability, FileId, FilePosition, SourceDatabaseExt}; use rustc_hash::FxHashSet; use std::sync::Arc; use test_utils::mark; @@ -59,15 +59,21 @@ fn parser_undefined_placeholder_in_replacement() { ); } -pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { +/// `code` may optionally contain a cursor marker `<|>`. If it doesn't, then the position will be +/// the start of the file. +pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FilePosition) { use ra_db::fixture::WithFixture; use ra_ide_db::symbol_index::SymbolsDatabase; - let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code); - let mut db = db; + let (mut db, position) = if code.contains(test_utils::CURSOR_MARKER) { + ra_ide_db::RootDatabase::with_position(code) + } else { + let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code); + (db, FilePosition { file_id, offset: 0.into() }) + }; let mut local_roots = FxHashSet::default(); local_roots.insert(ra_db::fixture::WORKSPACE); db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); - (db, file_id) + (db, position) } fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { @@ -75,8 +81,8 @@ fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { } fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { - let (db, file_id) = single_file(input); - let mut match_finder = MatchFinder::new(&db); + let (db, position) = single_file(input); + let mut match_finder = MatchFinder::in_context(&db, position); for rule in rules { let rule: SsrRule = rule.parse().unwrap(); match_finder.add_rule(rule); @@ -85,10 +91,10 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { if edits.is_empty() { panic!("No edits were made"); } - assert_eq!(edits[0].file_id, file_id); + assert_eq!(edits[0].file_id, position.file_id); // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters // stuff. - let mut actual = db.file_text(file_id).to_string(); + let mut actual = db.file_text(position.file_id).to_string(); edits[0].edit.apply(&mut actual); expected.assert_eq(&actual); } @@ -106,34 +112,34 @@ fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: } fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { - let (db, file_id) = single_file(code); - let mut match_finder = MatchFinder::new(&db); + let (db, position) = single_file(code); + let mut match_finder = MatchFinder::in_context(&db, position); match_finder.add_search_pattern(pattern.parse().unwrap()); let matched_strings: Vec = match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect(); if matched_strings != expected && !expected.is_empty() { - print_match_debug_info(&match_finder, file_id, &expected[0]); + print_match_debug_info(&match_finder, position.file_id, &expected[0]); } assert_eq!(matched_strings, expected); } fn assert_no_match(pattern: &str, code: &str) { - let (db, file_id) = single_file(code); - let mut match_finder = MatchFinder::new(&db); + let (db, position) = single_file(code); + let mut match_finder = MatchFinder::in_context(&db, position); match_finder.add_search_pattern(pattern.parse().unwrap()); let matches = match_finder.matches().flattened().matches; if !matches.is_empty() { - print_match_debug_info(&match_finder, file_id, &matches[0].matched_text()); + print_match_debug_info(&match_finder, position.file_id, &matches[0].matched_text()); panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches); } } fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) { - let (db, file_id) = single_file(code); - let mut match_finder = MatchFinder::new(&db); + let (db, position) = single_file(code); + let mut match_finder = MatchFinder::in_context(&db, position); match_finder.add_search_pattern(pattern.parse().unwrap()); let mut reasons = Vec::new(); - for d in match_finder.debug_where_text_equal(file_id, snippet) { + for d in match_finder.debug_where_text_equal(position.file_id, snippet) { if let Some(reason) = d.match_failure_reason() { reasons.push(reason.to_owned()); } -- cgit v1.2.3 From 757f755c29e041fd319af466d7d0418f54cb090a Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 16:46:29 +1000 Subject: SSR: Match paths based on what they resolve to Also render template paths appropriately for their context. --- crates/ra_ssr/src/tests.rs | 142 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 4 deletions(-) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 63d527894..33742dc8e 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -85,7 +85,7 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { let mut match_finder = MatchFinder::in_context(&db, position); for rule in rules { let rule: SsrRule = rule.parse().unwrap(); - match_finder.add_rule(rule); + match_finder.add_rule(rule).unwrap(); } let edits = match_finder.edits(); if edits.is_empty() { @@ -114,7 +114,7 @@ fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { let (db, position) = single_file(code); let mut match_finder = MatchFinder::in_context(&db, position); - match_finder.add_search_pattern(pattern.parse().unwrap()); + match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap(); let matched_strings: Vec = match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect(); if matched_strings != expected && !expected.is_empty() { @@ -126,7 +126,7 @@ fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { fn assert_no_match(pattern: &str, code: &str) { let (db, position) = single_file(code); let mut match_finder = MatchFinder::in_context(&db, position); - match_finder.add_search_pattern(pattern.parse().unwrap()); + match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap(); let matches = match_finder.matches().flattened().matches; if !matches.is_empty() { print_match_debug_info(&match_finder, position.file_id, &matches[0].matched_text()); @@ -137,7 +137,7 @@ fn assert_no_match(pattern: &str, code: &str) { fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) { let (db, position) = single_file(code); let mut match_finder = MatchFinder::in_context(&db, position); - match_finder.add_search_pattern(pattern.parse().unwrap()); + match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap(); let mut reasons = Vec::new(); for d in match_finder.debug_where_text_equal(position.file_id, snippet) { if let Some(reason) = d.match_failure_reason() { @@ -350,6 +350,60 @@ fn match_pattern() { assert_matches("Some($a)", "struct Some(); fn f() {if let Some(x) = foo() {}}", &["Some(x)"]); } +// If our pattern has a full path, e.g. a::b::c() and the code has c(), but c resolves to +// a::b::c, then we should match. +#[test] +fn match_fully_qualified_fn_path() { + let code = r#" + mod a { + pub mod b { + pub fn c(_: i32) {} + } + } + use a::b::c; + fn f1() { + c(42); + } + "#; + assert_matches("a::b::c($a)", code, &["c(42)"]); +} + +#[test] +fn match_resolved_type_name() { + let code = r#" + mod m1 { + pub mod m2 { + pub trait Foo {} + } + } + mod m3 { + trait Foo {} + fn f1(f: Option<&dyn Foo>) {} + } + mod m4 { + use crate::m1::m2::Foo; + fn f1(f: Option<&dyn Foo>) {} + } + "#; + assert_matches("m1::m2::Foo<$t>", code, &["Foo"]); +} + +#[test] +fn type_arguments_within_path() { + mark::check!(type_arguments_within_path); + let code = r#" + mod foo { + pub struct Bar {t: T} + impl Bar { + pub fn baz() {} + } + } + fn f1() {foo::Bar::::baz();} + "#; + assert_no_match("foo::Bar::::baz()", code); + assert_matches("foo::Bar::::baz()", code, &["foo::Bar::::baz()"]); +} + #[test] fn literal_constraint() { mark::check!(literal_constraint); @@ -482,6 +536,86 @@ fn replace_associated_function_call() { ); } +#[test] +fn replace_path_in_different_contexts() { + // Note the <|> inside module a::b which marks the point where the rule is interpreted. We + // replace foo with bar, but both need different path qualifiers in different contexts. In f4, + // foo is unqualified because of a use statement, however the replacement needs to be fully + // qualified. + assert_ssr_transform( + "c::foo() ==>> c::bar()", + r#" + mod a { + pub mod b {<|> + pub mod c { + pub fn foo() {} + pub fn bar() {} + fn f1() { foo() } + } + fn f2() { c::foo() } + } + fn f3() { b::c::foo() } + } + use a::b::c::foo; + fn f4() { foo() } + "#, + expect![[r#" + mod a { + pub mod b { + pub mod c { + pub fn foo() {} + pub fn bar() {} + fn f1() { bar() } + } + fn f2() { c::bar() } + } + fn f3() { b::c::bar() } + } + use a::b::c::foo; + fn f4() { a::b::c::bar() } + "#]], + ); +} + +#[test] +fn replace_associated_function_with_generics() { + assert_ssr_transform( + "c::Foo::<$a>::new() ==>> d::Bar::<$a>::default()", + r#" + mod c { + pub struct Foo {v: T} + impl Foo { pub fn new() {} } + fn f1() { + Foo::::new(); + } + } + mod d { + pub struct Bar {v: T} + impl Bar { pub fn default() {} } + fn f1() { + super::c::Foo::::new(); + } + } + "#, + expect![[r#" + mod c { + pub struct Foo {v: T} + impl Foo { pub fn new() {} } + fn f1() { + crate::d::Bar::::default(); + } + } + mod d { + pub struct Bar {v: T} + impl Bar { pub fn default() {} } + fn f1() { + Bar::::default(); + } + } + "#]], + ); +} + #[test] fn replace_type() { assert_ssr_transform( -- cgit v1.2.3 From 8d09ab86edfc01405fd0045bef82e0642efd5f01 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Thu, 23 Jul 2020 21:28:31 +1000 Subject: SSR: Disable matching within use declarations It currently does the wrong thing when the use declaration contains braces. --- crates/ra_ssr/src/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 33742dc8e..f564c6129 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -804,3 +804,26 @@ fn overlapping_possible_matches() { &["foo(foo(42))", "foo(foo(foo(foo(42))))"], ); } + +#[test] +fn use_declaration_with_braces() { + // It would be OK for a path rule to match and alter a use declaration. We shouldn't mess it up + // though. In particular, we must not change `use foo::{baz, bar}` to `use foo::{baz, + // foo2::bar2}`. + mark::check!(use_declaration_with_braces); + assert_ssr_transform( + "foo::bar ==>> foo2::bar2", + r#" + mod foo { pub fn bar() {} pub fn baz() {} } + mod foo2 { pub fn bar2() {} } + use foo::{baz, bar}; + fn main() { bar() } + "#, + expect![[" + mod foo { pub fn bar() {} pub fn baz() {} } + mod foo2 { pub fn bar2() {} } + use foo::{baz, bar}; + fn main() { foo2::bar2() } + "]], + ) +} -- cgit v1.2.3 From 3dac31fe80b9d7279e87b94615b0d55805e83412 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Fri, 24 Jul 2020 20:53:48 +1000 Subject: SSR: Allow function calls to match method calls This differs from how this used to work before I removed it in that: a) It's only one direction. Function calls in the pattern can match method calls in the code, but not the other way around. b) We now check that the function call in the pattern resolves to the same function as the method call in the code. The lack of (b) was the reason I felt the need to remove the feature before. --- crates/ra_ssr/src/tests.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'crates/ra_ssr/src/tests.rs') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index f564c6129..b38807c0f 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -827,3 +827,61 @@ fn use_declaration_with_braces() { "]], ) } + +#[test] +fn ufcs_matches_method_call() { + let code = r#" + struct Foo {} + impl Foo { + fn new(_: i32) -> Foo { Foo {} } + fn do_stuff(&self, _: i32) {} + } + struct Bar {} + impl Bar { + fn new(_: i32) -> Bar { Bar {} } + fn do_stuff(&self, v: i32) {} + } + fn main() { + let b = Bar {}; + let f = Foo {}; + b.do_stuff(1); + f.do_stuff(2); + Foo::new(4).do_stuff(3); + // Too many / too few args - should never match + f.do_stuff(2, 10); + f.do_stuff(); + } + "#; + assert_matches("Foo::do_stuff($a, $b)", code, &["f.do_stuff(2)", "Foo::new(4).do_stuff(3)"]); + // The arguments needs special handling in the case of a function call matching a method call + // and the first argument is different. + assert_matches("Foo::do_stuff($a, 2)", code, &["f.do_stuff(2)"]); + assert_matches("Foo::do_stuff(Foo::new(4), $b)", code, &["Foo::new(4).do_stuff(3)"]); + + assert_ssr_transform( + "Foo::do_stuff(Foo::new($a), $b) ==>> Bar::new($b).do_stuff($a)", + code, + expect![[r#" + struct Foo {} + impl Foo { + fn new(_: i32) -> Foo { Foo {} } + fn do_stuff(&self, _: i32) {} + } + struct Bar {} + impl Bar { + fn new(_: i32) -> Bar { Bar {} } + fn do_stuff(&self, v: i32) {} + } + fn main() { + let b = Bar {}; + let f = Foo {}; + b.do_stuff(1); + f.do_stuff(2); + Bar::new(3).do_stuff(4); + // Too many / too few args - should never match + f.do_stuff(2, 10); + f.do_stuff(); + } + "#]], + ); +} -- cgit v1.2.3