diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs')
-rw-r--r-- | src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs | 1084 |
1 files changed, 1084 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs new file mode 100644 index 000000000..59673af32 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs @@ -0,0 +1,1084 @@ +use base_db::fixture::WithFixture; +use hir::PrefixKind; +use stdx::trim_indent; +use test_utils::{assert_eq_text, CURSOR_MARKER}; + +use super::*; + +#[test] +fn trailing_comment_in_empty_file() { + check( + "foo::bar", + r#" +struct Struct; +// 0 = 1 +"#, + r#" +use foo::bar; + +struct Struct; +// 0 = 1 +"#, + ImportGranularity::Crate, + ); +} + +#[test] +fn respects_cfg_attr_fn() { + check( + r"bar::Bar", + r#" +#[cfg(test)] +fn foo() {$0} +"#, + r#" +#[cfg(test)] +fn foo() { + use bar::Bar; +} +"#, + ImportGranularity::Crate, + ); +} + +#[test] +fn respects_cfg_attr_const() { + check( + r"bar::Bar", + r#" +#[cfg(test)] +const FOO: Bar = {$0}; +"#, + r#" +#[cfg(test)] +const FOO: Bar = { + use bar::Bar; +}; +"#, + ImportGranularity::Crate, + ); +} + +#[test] +fn insert_skips_lone_glob_imports() { + check( + "use foo::baz::A", + r" +use foo::bar::*; +", + r" +use foo::bar::*; +use foo::baz::A; +", + ImportGranularity::Crate, + ); +} + +#[test] +fn insert_not_group() { + cov_mark::check!(insert_no_grouping_last); + check_with_config( + "use external_crate2::bar::A", + r" +use std::bar::B; +use external_crate::bar::A; +use crate::bar::A; +use self::bar::A; +use super::bar::A;", + r" +use std::bar::B; +use external_crate::bar::A; +use crate::bar::A; +use self::bar::A; +use super::bar::A; +use external_crate2::bar::A;", + &InsertUseConfig { + granularity: ImportGranularity::Item, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: false, + skip_glob_imports: true, + }, + ); +} + +#[test] +fn insert_existing() { + check_crate("std::fs", "use std::fs;", "use std::fs;") +} + +#[test] +fn insert_start() { + check_none( + "std::bar::AA", + r" +use std::bar::B; +use std::bar::D; +use std::bar::F; +use std::bar::G;", + r" +use std::bar::AA; +use std::bar::B; +use std::bar::D; +use std::bar::F; +use std::bar::G;", + ) +} + +#[test] +fn insert_start_indent() { + check_none( + "std::bar::AA", + r" + use std::bar::B; + use std::bar::C;", + r" + use std::bar::AA; + use std::bar::B; + use std::bar::C;", + ); +} + +#[test] +fn insert_middle() { + cov_mark::check!(insert_group); + check_none( + "std::bar::EE", + r" +use std::bar::A; +use std::bar::D; +use std::bar::F; +use std::bar::G;", + r" +use std::bar::A; +use std::bar::D; +use std::bar::EE; +use std::bar::F; +use std::bar::G;", + ) +} + +#[test] +fn insert_middle_indent() { + check_none( + "std::bar::EE", + r" + use std::bar::A; + use std::bar::D; + use std::bar::F; + use std::bar::G;", + r" + use std::bar::A; + use std::bar::D; + use std::bar::EE; + use std::bar::F; + use std::bar::G;", + ) +} + +#[test] +fn insert_end() { + cov_mark::check!(insert_group_last); + check_none( + "std::bar::ZZ", + r" +use std::bar::A; +use std::bar::D; +use std::bar::F; +use std::bar::G;", + r" +use std::bar::A; +use std::bar::D; +use std::bar::F; +use std::bar::G; +use std::bar::ZZ;", + ) +} + +#[test] +fn insert_end_indent() { + check_none( + "std::bar::ZZ", + r" + use std::bar::A; + use std::bar::D; + use std::bar::F; + use std::bar::G;", + r" + use std::bar::A; + use std::bar::D; + use std::bar::F; + use std::bar::G; + use std::bar::ZZ;", + ) +} + +#[test] +fn insert_middle_nested() { + check_none( + "std::bar::EE", + r" +use std::bar::A; +use std::bar::{D, Z}; // example of weird imports due to user +use std::bar::F; +use std::bar::G;", + r" +use std::bar::A; +use std::bar::EE; +use std::bar::{D, Z}; // example of weird imports due to user +use std::bar::F; +use std::bar::G;", + ) +} + +#[test] +fn insert_middle_groups() { + check_none( + "foo::bar::GG", + r" + use std::bar::A; + use std::bar::D; + + use foo::bar::F; + use foo::bar::H;", + r" + use std::bar::A; + use std::bar::D; + + use foo::bar::F; + use foo::bar::GG; + use foo::bar::H;", + ) +} + +#[test] +fn insert_first_matching_group() { + check_none( + "foo::bar::GG", + r" + use foo::bar::A; + use foo::bar::D; + + use std; + + use foo::bar::F; + use foo::bar::H;", + r" + use foo::bar::A; + use foo::bar::D; + use foo::bar::GG; + + use std; + + use foo::bar::F; + use foo::bar::H;", + ) +} + +#[test] +fn insert_missing_group_std() { + cov_mark::check!(insert_group_new_group); + check_none( + "std::fmt", + r" + use foo::bar::A; + use foo::bar::D;", + r" + use std::fmt; + + use foo::bar::A; + use foo::bar::D;", + ) +} + +#[test] +fn insert_missing_group_self() { + cov_mark::check!(insert_group_no_group); + check_none( + "self::fmt", + r" +use foo::bar::A; +use foo::bar::D;", + r" +use foo::bar::A; +use foo::bar::D; + +use self::fmt;", + ) +} + +#[test] +fn insert_no_imports() { + check_crate( + "foo::bar", + "fn main() {}", + r"use foo::bar; + +fn main() {}", + ) +} + +#[test] +fn insert_empty_file() { + cov_mark::check_count!(insert_empty_file, 2); + + // Default configuration + // empty files will get two trailing newlines + // this is due to the test case insert_no_imports above + check_crate( + "foo::bar", + "", + r"use foo::bar; + +", + ); + + // "not group" configuration + check_with_config( + "use external_crate2::bar::A", + r"", + r"use external_crate2::bar::A; + +", + &InsertUseConfig { + granularity: ImportGranularity::Item, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: false, + skip_glob_imports: true, + }, + ); +} + +#[test] +fn insert_empty_module() { + cov_mark::check_count!(insert_empty_module, 2); + + // Default configuration + check( + "foo::bar", + r" +mod x {$0} +", + r" +mod x { + use foo::bar; +} +", + ImportGranularity::Item, + ); + + // "not group" configuration + check_with_config( + "foo::bar", + r"mod x {$0}", + r"mod x { + use foo::bar; +}", + &InsertUseConfig { + granularity: ImportGranularity::Item, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: false, + skip_glob_imports: true, + }, + ); +} + +#[test] +fn insert_after_inner_attr() { + cov_mark::check_count!(insert_empty_inner_attr, 2); + + // Default configuration + check_crate( + "foo::bar", + r"#![allow(unused_imports)]", + r"#![allow(unused_imports)] + +use foo::bar;", + ); + + // "not group" configuration + check_with_config( + "foo::bar", + r"#![allow(unused_imports)]", + r"#![allow(unused_imports)] + +use foo::bar;", + &InsertUseConfig { + granularity: ImportGranularity::Item, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: false, + skip_glob_imports: true, + }, + ); +} + +#[test] +fn insert_after_inner_attr2() { + check_crate( + "foo::bar", + r"#![allow(unused_imports)] + +#![no_std] +fn main() {}", + r"#![allow(unused_imports)] + +#![no_std] + +use foo::bar; +fn main() {}", + ); +} + +#[test] +fn inserts_after_single_line_inner_comments() { + check_none( + "foo::bar::Baz", + "//! Single line inner comments do not allow any code before them.", + r#"//! Single line inner comments do not allow any code before them. + +use foo::bar::Baz;"#, + ); + check_none( + "foo::bar::Baz", + r"mod foo { + //! Single line inner comments do not allow any code before them. +$0 +}", + r"mod foo { + //! Single line inner comments do not allow any code before them. + + use foo::bar::Baz; + +}", + ); +} + +#[test] +fn inserts_after_single_line_comments() { + check_none( + "foo::bar::Baz", + "// Represents a possible license header and/or general module comments", + r#"// Represents a possible license header and/or general module comments + +use foo::bar::Baz;"#, + ); +} + +#[test] +fn inserts_after_shebang() { + check_none( + "foo::bar::Baz", + "#!/usr/bin/env rust", + r#"#!/usr/bin/env rust + +use foo::bar::Baz;"#, + ); +} + +#[test] +fn inserts_after_multiple_single_line_comments() { + check_none( + "foo::bar::Baz", + "// Represents a possible license header and/or general module comments +// Second single-line comment +// Third single-line comment", + r#"// Represents a possible license header and/or general module comments +// Second single-line comment +// Third single-line comment + +use foo::bar::Baz;"#, + ); +} + +#[test] +fn inserts_before_single_line_item_comments() { + check_none( + "foo::bar::Baz", + r#"// Represents a comment about a function +fn foo() {}"#, + r#"use foo::bar::Baz; + +// Represents a comment about a function +fn foo() {}"#, + ); +} + +#[test] +fn inserts_after_single_line_header_comments_and_before_item() { + check_none( + "foo::bar::Baz", + r#"// Represents a possible license header +// Line two of possible license header + +fn foo() {}"#, + r#"// Represents a possible license header +// Line two of possible license header + +use foo::bar::Baz; + +fn foo() {}"#, + ); +} + +#[test] +fn inserts_after_multiline_inner_comments() { + check_none( + "foo::bar::Baz", + r#"/*! Multiline inner comments do not allow any code before them. */ + +/*! Still an inner comment, cannot place any code before. */ +fn main() {}"#, + r#"/*! Multiline inner comments do not allow any code before them. */ + +/*! Still an inner comment, cannot place any code before. */ + +use foo::bar::Baz; +fn main() {}"#, + ) +} + +#[test] +fn inserts_after_all_inner_items() { + check_none( + "foo::bar::Baz", + r#"#![allow(unused_imports)] +/*! Multiline line comment 2 */ + + +//! Single line comment 1 +#![no_std] +//! Single line comment 2 +fn main() {}"#, + r#"#![allow(unused_imports)] +/*! Multiline line comment 2 */ + + +//! Single line comment 1 +#![no_std] +//! Single line comment 2 + +use foo::bar::Baz; +fn main() {}"#, + ) +} + +#[test] +fn merge_groups() { + check_module("std::io", r"use std::fmt;", r"use std::{fmt, io};") +} + +#[test] +fn merge_groups_last() { + check_module( + "std::io", + r"use std::fmt::{Result, Display};", + r"use std::fmt::{Result, Display}; +use std::io;", + ) +} + +#[test] +fn merge_last_into_self() { + check_module("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};"); +} + +#[test] +fn merge_groups_full() { + check_crate( + "std::io", + r"use std::fmt::{Result, Display};", + r"use std::{fmt::{Result, Display}, io};", + ) +} + +#[test] +fn merge_groups_long_full() { + check_crate("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Qux, Baz};") +} + +#[test] +fn merge_groups_long_last() { + check_module( + "std::foo::bar::Baz", + r"use std::foo::bar::Qux;", + r"use std::foo::bar::{Qux, Baz};", + ) +} + +#[test] +fn merge_groups_long_full_list() { + check_crate( + "std::foo::bar::Baz", + r"use std::foo::bar::{Qux, Quux};", + r"use std::foo::bar::{Qux, Quux, Baz};", + ) +} + +#[test] +fn merge_groups_long_last_list() { + check_module( + "std::foo::bar::Baz", + r"use std::foo::bar::{Qux, Quux};", + r"use std::foo::bar::{Qux, Quux, Baz};", + ) +} + +#[test] +fn merge_groups_long_full_nested() { + check_crate( + "std::foo::bar::Baz", + r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", + r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};", + ) +} + +#[test] +fn merge_groups_long_last_nested() { + check_module( + "std::foo::bar::Baz", + r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", + r"use std::foo::bar::Baz; +use std::foo::bar::{Qux, quux::{Fez, Fizz}};", + ) +} + +#[test] +fn merge_groups_full_nested_deep() { + check_crate( + "std::foo::bar::quux::Baz", + r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", + r"use std::foo::bar::{Qux, quux::{Fez, Fizz, Baz}};", + ) +} + +#[test] +fn merge_groups_full_nested_long() { + check_crate( + "std::foo::bar::Baz", + r"use std::{foo::bar::Qux};", + r"use std::{foo::bar::{Qux, Baz}};", + ); +} + +#[test] +fn merge_groups_last_nested_long() { + check_crate( + "std::foo::bar::Baz", + r"use std::{foo::bar::Qux};", + r"use std::{foo::bar::{Qux, Baz}};", + ); +} + +#[test] +fn merge_groups_skip_pub() { + check_crate( + "std::io", + r"pub use std::fmt::{Result, Display};", + r"pub use std::fmt::{Result, Display}; +use std::io;", + ) +} + +#[test] +fn merge_groups_skip_pub_crate() { + check_crate( + "std::io", + r"pub(crate) use std::fmt::{Result, Display};", + r"pub(crate) use std::fmt::{Result, Display}; +use std::io;", + ) +} + +#[test] +fn merge_groups_skip_attributed() { + check_crate( + "std::io", + r#" +#[cfg(feature = "gated")] use std::fmt::{Result, Display}; +"#, + r#" +#[cfg(feature = "gated")] use std::fmt::{Result, Display}; +use std::io; +"#, + ) +} + +#[test] +fn split_out_merge() { + // FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}` + // instead. + check_module( + "std::fmt::Result", + r"use std::{fmt, io};", + r"use std::fmt::Result; +use std::{fmt, io};", + ) +} + +#[test] +fn merge_into_module_import() { + check_crate("std::fmt::Result", r"use std::{fmt, io};", r"use std::{fmt::{self, Result}, io};") +} + +#[test] +fn merge_groups_self() { + check_crate("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") +} + +#[test] +fn merge_mod_into_glob() { + check_with_config( + "token::TokenKind", + r"use token::TokenKind::*;", + r"use token::TokenKind::{*, self};", + &InsertUseConfig { + granularity: ImportGranularity::Crate, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: false, + skip_glob_imports: false, + }, + ) + // FIXME: have it emit `use token::TokenKind::{self, *}`? +} + +#[test] +fn merge_self_glob() { + check_with_config( + "self", + r"use self::*;", + r"use self::{*, self};", + &InsertUseConfig { + granularity: ImportGranularity::Crate, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: false, + skip_glob_imports: false, + }, + ) + // FIXME: have it emit `use {self, *}`? +} + +#[test] +fn merge_glob() { + check_crate( + "syntax::SyntaxKind", + r" +use syntax::{SyntaxKind::*};", + r" +use syntax::{SyntaxKind::{*, self}};", + ) +} + +#[test] +fn merge_glob_nested() { + check_crate( + "foo::bar::quux::Fez", + r"use foo::bar::{Baz, quux::*};", + r"use foo::bar::{Baz, quux::{*, Fez}};", + ) +} + +#[test] +fn merge_nested_considers_first_segments() { + check_crate( + "hir_ty::display::write_bounds_like_dyn_trait", + r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter}, method_resolution};", + r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter, write_bounds_like_dyn_trait}, method_resolution};", + ); +} + +#[test] +fn skip_merge_last_too_long() { + check_module( + "foo::bar", + r"use foo::bar::baz::Qux;", + r"use foo::bar; +use foo::bar::baz::Qux;", + ); +} + +#[test] +fn skip_merge_last_too_long2() { + check_module( + "foo::bar::baz::Qux", + r"use foo::bar;", + r"use foo::bar; +use foo::bar::baz::Qux;", + ); +} + +#[test] +fn insert_short_before_long() { + check_none( + "foo::bar", + r"use foo::bar::baz::Qux;", + r"use foo::bar; +use foo::bar::baz::Qux;", + ); +} + +#[test] +fn merge_last_fail() { + check_merge_only_fail( + r"use foo::bar::{baz::{Qux, Fez}};", + r"use foo::bar::{baaz::{Quux, Feez}};", + MergeBehavior::Module, + ); +} + +#[test] +fn merge_last_fail1() { + check_merge_only_fail( + r"use foo::bar::{baz::{Qux, Fez}};", + r"use foo::bar::baaz::{Quux, Feez};", + MergeBehavior::Module, + ); +} + +#[test] +fn merge_last_fail2() { + check_merge_only_fail( + r"use foo::bar::baz::{Qux, Fez};", + r"use foo::bar::{baaz::{Quux, Feez}};", + MergeBehavior::Module, + ); +} + +#[test] +fn merge_last_fail3() { + check_merge_only_fail( + r"use foo::bar::baz::{Qux, Fez};", + r"use foo::bar::baaz::{Quux, Feez};", + MergeBehavior::Module, + ); +} + +#[test] +fn guess_empty() { + check_guess("", ImportGranularityGuess::Unknown); +} + +#[test] +fn guess_single() { + check_guess(r"use foo::{baz::{qux, quux}, bar};", ImportGranularityGuess::Crate); + check_guess(r"use foo::bar;", ImportGranularityGuess::Unknown); + check_guess(r"use foo::bar::{baz, qux};", ImportGranularityGuess::CrateOrModule); +} + +#[test] +fn guess_unknown() { + check_guess( + r" +use foo::bar::baz; +use oof::rab::xuq; +", + ImportGranularityGuess::Unknown, + ); +} + +#[test] +fn guess_item() { + check_guess( + r" +use foo::bar::baz; +use foo::bar::qux; +", + ImportGranularityGuess::Item, + ); +} + +#[test] +fn guess_module_or_item() { + check_guess( + r" +use foo::bar::Bar; +use foo::qux; +", + ImportGranularityGuess::ModuleOrItem, + ); + check_guess( + r" +use foo::bar::Bar; +use foo::bar; +", + ImportGranularityGuess::ModuleOrItem, + ); +} + +#[test] +fn guess_module() { + check_guess( + r" +use foo::bar::baz; +use foo::bar::{qux, quux}; +", + ImportGranularityGuess::Module, + ); + // this is a rather odd case, technically this file isn't following any style properly. + check_guess( + r" +use foo::bar::baz; +use foo::{baz::{qux, quux}, bar}; +", + ImportGranularityGuess::Module, + ); + check_guess( + r" +use foo::bar::Bar; +use foo::baz::Baz; +use foo::{Foo, Qux}; +", + ImportGranularityGuess::Module, + ); +} + +#[test] +fn guess_crate_or_module() { + check_guess( + r" +use foo::bar::baz; +use oof::bar::{qux, quux}; +", + ImportGranularityGuess::CrateOrModule, + ); +} + +#[test] +fn guess_crate() { + check_guess( + r" +use frob::bar::baz; +use foo::{baz::{qux, quux}, bar}; +", + ImportGranularityGuess::Crate, + ); +} + +#[test] +fn guess_skips_differing_vis() { + check_guess( + r" +use foo::bar::baz; +pub use foo::bar::qux; +", + ImportGranularityGuess::Unknown, + ); +} + +#[test] +fn guess_skips_differing_attrs() { + check_guess( + r" +pub use foo::bar::baz; +#[doc(hidden)] +pub use foo::bar::qux; +", + ImportGranularityGuess::Unknown, + ); +} + +#[test] +fn guess_grouping_matters() { + check_guess( + r" +use foo::bar::baz; +use oof::bar::baz; +use foo::bar::qux; +", + ImportGranularityGuess::Unknown, + ); +} + +fn check_with_config( + path: &str, + ra_fixture_before: &str, + ra_fixture_after: &str, + config: &InsertUseConfig, +) { + let (db, file_id, pos) = if ra_fixture_before.contains(CURSOR_MARKER) { + let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture_before); + (db, file_id, Some(range_or_offset)) + } else { + let (db, file_id) = RootDatabase::with_single_file(ra_fixture_before); + (db, file_id, None) + }; + let sema = &Semantics::new(&db); + let source_file = sema.parse(file_id); + let syntax = source_file.syntax().clone_for_update(); + let file = pos + .and_then(|pos| syntax.token_at_offset(pos.expect_offset()).next()?.parent()) + .and_then(|it| ImportScope::find_insert_use_container(&it, sema)) + .or_else(|| ImportScope::from(syntax)) + .unwrap(); + let path = ast::SourceFile::parse(&format!("use {};", path)) + .tree() + .syntax() + .descendants() + .find_map(ast::Path::cast) + .unwrap(); + + insert_use(&file, path, config); + let result = file.as_syntax_node().ancestors().last().unwrap().to_string(); + assert_eq_text!(&trim_indent(ra_fixture_after), &result); +} + +fn check( + path: &str, + ra_fixture_before: &str, + ra_fixture_after: &str, + granularity: ImportGranularity, +) { + check_with_config( + path, + ra_fixture_before, + ra_fixture_after, + &InsertUseConfig { + granularity, + enforce_granularity: true, + prefix_kind: PrefixKind::Plain, + group: true, + skip_glob_imports: true, + }, + ) +} + +fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate) +} + +fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module) +} + +fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item) +} + +fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { + let use0 = ast::SourceFile::parse(ra_fixture0) + .tree() + .syntax() + .descendants() + .find_map(ast::Use::cast) + .unwrap(); + + let use1 = ast::SourceFile::parse(ra_fixture1) + .tree() + .syntax() + .descendants() + .find_map(ast::Use::cast) + .unwrap(); + + let result = try_merge_imports(&use0, &use1, mb); + assert_eq!(result.map(|u| u.to_string()), None); +} + +fn check_guess(ra_fixture: &str, expected: ImportGranularityGuess) { + let syntax = ast::SourceFile::parse(ra_fixture).tree().syntax().clone(); + let file = ImportScope::from(syntax).unwrap(); + assert_eq!(super::guess_granularity_from_scope(&file), expected); +} |