aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/annotations.rs83
-rw-r--r--crates/ide/src/diagnostics.rs163
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs156
-rw-r--r--crates/ide/src/file_structure.rs228
-rw-r--r--crates/ide/src/goto_definition.rs11
-rw-r--r--crates/ide/src/goto_implementation.rs130
-rw-r--r--crates/ide/src/inlay_hints.rs2
-rw-r--r--crates/ide/src/join_lines.rs4
-rw-r--r--crates/ide/src/lib.rs15
-rw-r--r--crates/ide/src/parent_module.rs79
-rw-r--r--crates/ide/src/references.rs26
-rw-r--r--crates/ide/src/references/rename.rs3
-rw-r--r--crates/ide/src/runnables.rs445
-rw-r--r--crates/ide/src/typing.rs3
14 files changed, 1149 insertions, 199 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 2e8e82b70..72492f826 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -1,12 +1,13 @@
1use hir::Semantics; 1use either::Either;
2use hir::{HasSource, Semantics};
2use ide_db::{ 3use ide_db::{
3 base_db::{FileId, FilePosition, FileRange, SourceDatabase}, 4 base_db::{FileId, FilePosition, FileRange},
4 RootDatabase, SymbolKind, 5 helpers::visit_file_defs,
6 RootDatabase,
5}; 7};
6use syntax::TextRange; 8use syntax::{ast::NameOwner, AstNode, TextRange, TextSize};
7 9
8use crate::{ 10use crate::{
9 file_structure::file_structure,
10 fn_references::find_all_methods, 11 fn_references::find_all_methods,
11 goto_implementation::goto_implementation, 12 goto_implementation::goto_implementation,
12 references::find_all_refs, 13 references::find_all_refs,
@@ -75,39 +76,56 @@ pub(crate) fn annotations(
75 } 76 }
76 } 77 }
77 78
78 file_structure(&db.parse(file_id).tree()) 79 visit_file_defs(&Semantics::new(db), file_id, &mut |def| match def {
79 .into_iter() 80 Either::Left(def) => {
80 .filter(|node| { 81 let node = match def {
81 matches!( 82 hir::ModuleDef::Const(konst) => {
82 node.kind, 83 konst.source(db).and_then(|node| range_and_position_of(&node.value))
83 SymbolKind::Trait 84 }
84 | SymbolKind::Struct 85 hir::ModuleDef::Trait(trait_) => {
85 | SymbolKind::Enum 86 trait_.source(db).and_then(|node| range_and_position_of(&node.value))
86 | SymbolKind::Union 87 }
87 | SymbolKind::Const 88 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
88 ) 89 strukt.source(db).and_then(|node| range_and_position_of(&node.value))
89 }) 90 }
90 .for_each(|node| { 91 hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => {
91 if config.annotate_impls && node.kind != SymbolKind::Const { 92 enum_.source(db).and_then(|node| range_and_position_of(&node.value))
93 }
94 hir::ModuleDef::Adt(hir::Adt::Union(union)) => {
95 union.source(db).and_then(|node| range_and_position_of(&node.value))
96 }
97 _ => None,
98 };
99 let (offset, range) = match node {
100 Some(node) => node,
101 None => return,
102 };
103
104 if config.annotate_impls && !matches!(def, hir::ModuleDef::Const(_)) {
92 annotations.push(Annotation { 105 annotations.push(Annotation {
93 range: node.node_range, 106 range,
94 kind: AnnotationKind::HasImpls { 107 kind: AnnotationKind::HasImpls {
95 position: FilePosition { file_id, offset: node.navigation_range.start() }, 108 position: FilePosition { file_id, offset },
96 data: None, 109 data: None,
97 }, 110 },
98 }); 111 });
99 } 112 }
100
101 if config.annotate_references { 113 if config.annotate_references {
102 annotations.push(Annotation { 114 annotations.push(Annotation {
103 range: node.node_range, 115 range,
104 kind: AnnotationKind::HasReferences { 116 kind: AnnotationKind::HasReferences {
105 position: FilePosition { file_id, offset: node.navigation_range.start() }, 117 position: FilePosition { file_id, offset },
106 data: None, 118 data: None,
107 }, 119 },
108 }); 120 });
109 } 121 }
110 }); 122
123 fn range_and_position_of(node: &dyn NameOwner) -> Option<(TextSize, TextRange)> {
124 Some((node.name()?.syntax().text_range().start(), node.syntax().text_range()))
125 }
126 }
127 Either::Right(_) => (),
128 });
111 129
112 if config.annotate_method_references { 130 if config.annotate_method_references {
113 annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation { 131 annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation {
@@ -934,4 +952,19 @@ mod tests {
934 "#]], 952 "#]],
935 ); 953 );
936 } 954 }
955
956 #[test]
957 fn test_no_annotations_outside_module_tree() {
958 check(
959 r#"
960//- /foo.rs
961struct Foo;
962//- /lib.rs
963// this file comes last since `check` checks the first file only
964"#,
965 expect![[r#"
966 []
967 "#]],
968 );
969 }
937} 970}
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index fe32f39b6..22697a537 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -6,6 +6,7 @@
6 6
7mod fixes; 7mod fixes;
8mod field_shorthand; 8mod field_shorthand;
9mod unlinked_file;
9 10
10use std::cell::RefCell; 11use std::cell::RefCell;
11 12
@@ -22,6 +23,7 @@ use syntax::{
22 SyntaxNode, SyntaxNodePtr, TextRange, 23 SyntaxNode, SyntaxNodePtr, TextRange,
23}; 24};
24use text_edit::TextEdit; 25use text_edit::TextEdit;
26use unlinked_file::UnlinkedFile;
25 27
26use crate::{FileId, Label, SourceChange}; 28use crate::{FileId, Label, SourceChange};
27 29
@@ -156,6 +158,18 @@ pub(crate) fn diagnostics(
156 .with_code(Some(d.code())), 158 .with_code(Some(d.code())),
157 ); 159 );
158 }) 160 })
161 .on::<UnlinkedFile, _>(|d| {
162 // Override severity and mark as unused.
163 res.borrow_mut().push(
164 Diagnostic::hint(
165 sema.diagnostics_display_range(d.display_source()).range,
166 d.message(),
167 )
168 .with_unused(true)
169 .with_fix(d.fix(&sema))
170 .with_code(Some(d.code())),
171 );
172 })
159 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { 173 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
160 // Use more accurate position if available. 174 // Use more accurate position if available.
161 let display_range = d 175 let display_range = d
@@ -197,9 +211,13 @@ pub(crate) fn diagnostics(
197 ); 211 );
198 }); 212 });
199 213
200 if let Some(m) = sema.to_module_def(file_id) { 214 match sema.to_module_def(file_id) {
201 m.diagnostics(db, &mut sink); 215 Some(m) => m.diagnostics(db, &mut sink),
202 }; 216 None => {
217 sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) });
218 }
219 }
220
203 drop(sink); 221 drop(sink);
204 res.into_inner() 222 res.into_inner()
205} 223}
@@ -307,6 +325,17 @@ mod tests {
307 ); 325 );
308 } 326 }
309 327
328 /// Checks that there's a diagnostic *without* fix at `$0`.
329 fn check_no_fix(ra_fixture: &str) {
330 let (analysis, file_position) = fixture::position(ra_fixture);
331 let diagnostic = analysis
332 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
333 .unwrap()
334 .pop()
335 .unwrap();
336 assert!(diagnostic.fix.is_none(), "got a fix when none was expected: {:?}", diagnostic);
337 }
338
310 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics 339 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
311 /// apply to the file containing the cursor. 340 /// apply to the file containing the cursor.
312 pub(crate) fn check_no_diagnostics(ra_fixture: &str) { 341 pub(crate) fn check_no_diagnostics(ra_fixture: &str) {
@@ -975,4 +1004,132 @@ impl TestStruct {
975 1004
976 check_fix(input, expected); 1005 check_fix(input, expected);
977 } 1006 }
1007
1008 #[test]
1009 fn unlinked_file_prepend_first_item() {
1010 cov_mark::check!(unlinked_file_prepend_before_first_item);
1011 check_fix(
1012 r#"
1013//- /main.rs
1014fn f() {}
1015//- /foo.rs
1016$0
1017"#,
1018 r#"
1019mod foo;
1020
1021fn f() {}
1022"#,
1023 );
1024 }
1025
1026 #[test]
1027 fn unlinked_file_append_mod() {
1028 cov_mark::check!(unlinked_file_append_to_existing_mods);
1029 check_fix(
1030 r#"
1031//- /main.rs
1032//! Comment on top
1033
1034mod preexisting;
1035
1036mod preexisting2;
1037
1038struct S;
1039
1040mod preexisting_bottom;)
1041//- /foo.rs
1042$0
1043"#,
1044 r#"
1045//! Comment on top
1046
1047mod preexisting;
1048
1049mod preexisting2;
1050mod foo;
1051
1052struct S;
1053
1054mod preexisting_bottom;)
1055"#,
1056 );
1057 }
1058
1059 #[test]
1060 fn unlinked_file_insert_in_empty_file() {
1061 cov_mark::check!(unlinked_file_empty_file);
1062 check_fix(
1063 r#"
1064//- /main.rs
1065//- /foo.rs
1066$0
1067"#,
1068 r#"
1069mod foo;
1070"#,
1071 );
1072 }
1073
1074 #[test]
1075 fn unlinked_file_old_style_modrs() {
1076 check_fix(
1077 r#"
1078//- /main.rs
1079mod submod;
1080//- /submod/mod.rs
1081// in mod.rs
1082//- /submod/foo.rs
1083$0
1084"#,
1085 r#"
1086// in mod.rs
1087mod foo;
1088"#,
1089 );
1090 }
1091
1092 #[test]
1093 fn unlinked_file_new_style_mod() {
1094 check_fix(
1095 r#"
1096//- /main.rs
1097mod submod;
1098//- /submod.rs
1099//- /submod/foo.rs
1100$0
1101"#,
1102 r#"
1103mod foo;
1104"#,
1105 );
1106 }
1107
1108 #[test]
1109 fn unlinked_file_with_cfg_off() {
1110 cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
1111 check_no_fix(
1112 r#"
1113//- /main.rs
1114#[cfg(never)]
1115mod foo;
1116
1117//- /foo.rs
1118$0
1119"#,
1120 );
1121 }
1122
1123 #[test]
1124 fn unlinked_file_with_cfg_on() {
1125 check_no_diagnostics(
1126 r#"
1127//- /main.rs
1128#[cfg(not(never))]
1129mod foo;
1130
1131//- /foo.rs
1132"#,
1133 );
1134 }
978} 1135}
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
new file mode 100644
index 000000000..c5741bf6b
--- /dev/null
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -0,0 +1,156 @@
1//! Diagnostic emitted for files that aren't part of any crate.
2
3use hir::{
4 db::DefDatabase,
5 diagnostics::{Diagnostic, DiagnosticCode},
6 InFile,
7};
8use ide_db::{
9 base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
10 source_change::SourceChange,
11 RootDatabase,
12};
13use syntax::{
14 ast::{self, ModuleItemOwner, NameOwner},
15 AstNode, SyntaxNodePtr,
16};
17use text_edit::TextEdit;
18
19use crate::Fix;
20
21use super::fixes::DiagnosticWithFix;
22
23#[derive(Debug)]
24pub(crate) struct UnlinkedFile {
25 pub(crate) file_id: FileId,
26 pub(crate) node: SyntaxNodePtr,
27}
28
29impl Diagnostic for UnlinkedFile {
30 fn code(&self) -> DiagnosticCode {
31 DiagnosticCode("unlinked-file")
32 }
33
34 fn message(&self) -> String {
35 "file not included in module tree".to_string()
36 }
37
38 fn display_source(&self) -> InFile<SyntaxNodePtr> {
39 InFile::new(self.file_id.into(), self.node.clone())
40 }
41
42 fn as_any(&self) -> &(dyn std::any::Any + Send + 'static) {
43 self
44 }
45}
46
47impl DiagnosticWithFix for UnlinkedFile {
48 fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Fix> {
49 // If there's an existing module that could add a `mod` item to include the unlinked file,
50 // suggest that as a fix.
51
52 let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id));
53 let our_path = source_root.path_for_file(&self.file_id)?;
54 let module_name = our_path.name_and_extension()?.0;
55
56 // Candidates to look for:
57 // - `mod.rs` in the same folder
58 // - we also check `main.rs` and `lib.rs`
59 // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
60 let parent = our_path.parent()?;
61 let mut paths =
62 vec![parent.join("mod.rs")?, parent.join("main.rs")?, parent.join("lib.rs")?];
63
64 // `submod/bla.rs` -> `submod.rs`
65 if let Some(newmod) = (|| {
66 let name = parent.name_and_extension()?.0;
67 parent.parent()?.join(&format!("{}.rs", name))
68 })() {
69 paths.push(newmod);
70 }
71
72 for path in &paths {
73 if let Some(parent_id) = source_root.file_for_path(path) {
74 for krate in sema.db.relevant_crates(*parent_id).iter() {
75 let crate_def_map = sema.db.crate_def_map(*krate);
76 for (_, module) in crate_def_map.modules() {
77 if module.origin.is_inline() {
78 // We don't handle inline `mod parent {}`s, they use different paths.
79 continue;
80 }
81
82 if module.origin.file_id() == Some(*parent_id) {
83 return make_fix(sema.db, *parent_id, module_name, self.file_id);
84 }
85 }
86 }
87 }
88 }
89
90 None
91 }
92}
93
94fn make_fix(
95 db: &RootDatabase,
96 parent_file_id: FileId,
97 new_mod_name: &str,
98 added_file_id: FileId,
99) -> Option<Fix> {
100 fn is_outline_mod(item: &ast::Item) -> bool {
101 matches!(item, ast::Item::Module(m) if m.item_list().is_none())
102 }
103
104 let mod_decl = format!("mod {};", new_mod_name);
105 let ast: ast::SourceFile = db.parse(parent_file_id).tree();
106
107 let mut builder = TextEdit::builder();
108
109 // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
110 // probably `#[cfg]`d out).
111 for item in ast.items() {
112 if let ast::Item::Module(m) = item {
113 if let Some(name) = m.name() {
114 if m.item_list().is_none() && name.to_string() == new_mod_name {
115 cov_mark::hit!(unlinked_file_skip_fix_when_mod_already_exists);
116 return None;
117 }
118 }
119 }
120 }
121
122 // If there are existing `mod m;` items, append after them (after the first group of them, rather).
123 match ast
124 .items()
125 .skip_while(|item| !is_outline_mod(item))
126 .take_while(|item| is_outline_mod(item))
127 .last()
128 {
129 Some(last) => {
130 cov_mark::hit!(unlinked_file_append_to_existing_mods);
131 builder.insert(last.syntax().text_range().end(), format!("\n{}", mod_decl));
132 }
133 None => {
134 // Prepend before the first item in the file.
135 match ast.items().next() {
136 Some(item) => {
137 cov_mark::hit!(unlinked_file_prepend_before_first_item);
138 builder.insert(item.syntax().text_range().start(), format!("{}\n\n", mod_decl));
139 }
140 None => {
141 // No items in the file, so just append at the end.
142 cov_mark::hit!(unlinked_file_empty_file);
143 builder.insert(ast.syntax().text_range().end(), format!("{}\n", mod_decl));
144 }
145 }
146 }
147 }
148
149 let edit = builder.finish();
150 let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
151 Some(Fix::new(
152 &format!("Insert `{}`", mod_decl),
153 SourceChange::from_text_edit(parent_file_id, edit),
154 trigger_range,
155 ))
156}
diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs
index 26793bdb4..9f879a66e 100644
--- a/crates/ide/src/file_structure.rs
+++ b/crates/ide/src/file_structure.rs
@@ -1,7 +1,8 @@
1use ide_db::SymbolKind; 1use ide_db::SymbolKind;
2use syntax::{ 2use syntax::{
3 ast::{self, AttrsOwner, GenericParamsOwner, NameOwner}, 3 ast::{self, AttrsOwner, GenericParamsOwner, NameOwner},
4 match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent, 4 match_ast, AstNode, AstToken, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken, TextRange,
5 WalkEvent,
5}; 6};
6 7
7#[derive(Debug, Clone)] 8#[derive(Debug, Clone)]
@@ -10,11 +11,17 @@ pub struct StructureNode {
10 pub label: String, 11 pub label: String,
11 pub navigation_range: TextRange, 12 pub navigation_range: TextRange,
12 pub node_range: TextRange, 13 pub node_range: TextRange,
13 pub kind: SymbolKind, 14 pub kind: StructureNodeKind,
14 pub detail: Option<String>, 15 pub detail: Option<String>,
15 pub deprecated: bool, 16 pub deprecated: bool,
16} 17}
17 18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
20pub enum StructureNodeKind {
21 SymbolKind(SymbolKind),
22 Region,
23}
24
18// Feature: File Structure 25// Feature: File Structure
19// 26//
20// Provides a tree of the symbols defined in the file. Can be used to 27// Provides a tree of the symbols defined in the file. Can be used to
@@ -32,34 +39,46 @@ pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
32 let mut res = Vec::new(); 39 let mut res = Vec::new();
33 let mut stack = Vec::new(); 40 let mut stack = Vec::new();
34 41
35 for event in file.syntax().preorder() { 42 for event in file.syntax().preorder_with_tokens() {
36 match event { 43 match event {
37 WalkEvent::Enter(node) => { 44 WalkEvent::Enter(NodeOrToken::Node(node)) => {
38 if let Some(mut symbol) = structure_node(&node) { 45 if let Some(mut symbol) = structure_node(&node) {
39 symbol.parent = stack.last().copied(); 46 symbol.parent = stack.last().copied();
40 stack.push(res.len()); 47 stack.push(res.len());
41 res.push(symbol); 48 res.push(symbol);
42 } 49 }
43 } 50 }
44 WalkEvent::Leave(node) => { 51 WalkEvent::Leave(NodeOrToken::Node(node)) => {
45 if structure_node(&node).is_some() { 52 if structure_node(&node).is_some() {
46 stack.pop().unwrap(); 53 stack.pop().unwrap();
47 } 54 }
48 } 55 }
56 WalkEvent::Enter(NodeOrToken::Token(token)) => {
57 if let Some(mut symbol) = structure_token(token) {
58 symbol.parent = stack.last().copied();
59 stack.push(res.len());
60 res.push(symbol);
61 }
62 }
63 WalkEvent::Leave(NodeOrToken::Token(token)) => {
64 if structure_token(token).is_some() {
65 stack.pop().unwrap();
66 }
67 }
49 } 68 }
50 } 69 }
51 res 70 res
52} 71}
53 72
54fn structure_node(node: &SyntaxNode) -> Option<StructureNode> { 73fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
55 fn decl<N: NameOwner + AttrsOwner>(node: N, kind: SymbolKind) -> Option<StructureNode> { 74 fn decl<N: NameOwner + AttrsOwner>(node: N, kind: StructureNodeKind) -> Option<StructureNode> {
56 decl_with_detail(&node, None, kind) 75 decl_with_detail(&node, None, kind)
57 } 76 }
58 77
59 fn decl_with_type_ref<N: NameOwner + AttrsOwner>( 78 fn decl_with_type_ref<N: NameOwner + AttrsOwner>(
60 node: &N, 79 node: &N,
61 type_ref: Option<ast::Type>, 80 type_ref: Option<ast::Type>,
62 kind: SymbolKind, 81 kind: StructureNodeKind,
63 ) -> Option<StructureNode> { 82 ) -> Option<StructureNode> {
64 let detail = type_ref.map(|type_ref| { 83 let detail = type_ref.map(|type_ref| {
65 let mut detail = String::new(); 84 let mut detail = String::new();
@@ -72,7 +91,7 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
72 fn decl_with_detail<N: NameOwner + AttrsOwner>( 91 fn decl_with_detail<N: NameOwner + AttrsOwner>(
73 node: &N, 92 node: &N,
74 detail: Option<String>, 93 detail: Option<String>,
75 kind: SymbolKind, 94 kind: StructureNodeKind,
76 ) -> Option<StructureNode> { 95 ) -> Option<StructureNode> {
77 let name = node.name()?; 96 let name = node.name()?;
78 97
@@ -120,18 +139,18 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
120 collapse_ws(ret_type.syntax(), &mut detail); 139 collapse_ws(ret_type.syntax(), &mut detail);
121 } 140 }
122 141
123 decl_with_detail(&it, Some(detail), SymbolKind::Function) 142 decl_with_detail(&it, Some(detail), StructureNodeKind::SymbolKind(SymbolKind::Function))
124 }, 143 },
125 ast::Struct(it) => decl(it, SymbolKind::Struct), 144 ast::Struct(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Struct)),
126 ast::Union(it) => decl(it, SymbolKind::Union), 145 ast::Union(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Union)),
127 ast::Enum(it) => decl(it, SymbolKind::Enum), 146 ast::Enum(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Enum)),
128 ast::Variant(it) => decl(it, SymbolKind::Variant), 147 ast::Variant(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Variant)),
129 ast::Trait(it) => decl(it, SymbolKind::Trait), 148 ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)),
130 ast::Module(it) => decl(it, SymbolKind::Module), 149 ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)),
131 ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), SymbolKind::TypeAlias), 150 ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)),
132 ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), SymbolKind::Field), 151 ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)),
133 ast::Const(it) => decl_with_type_ref(&it, it.ty(), SymbolKind::Const), 152 ast::Const(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Const)),
134 ast::Static(it) => decl_with_type_ref(&it, it.ty(), SymbolKind::Static), 153 ast::Static(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Static)),
135 ast::Impl(it) => { 154 ast::Impl(it) => {
136 let target_type = it.self_ty()?; 155 let target_type = it.self_ty()?;
137 let target_trait = it.trait_(); 156 let target_trait = it.trait_();
@@ -147,18 +166,38 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
147 label, 166 label,
148 navigation_range: target_type.syntax().text_range(), 167 navigation_range: target_type.syntax().text_range(),
149 node_range: it.syntax().text_range(), 168 node_range: it.syntax().text_range(),
150 kind: SymbolKind::Impl, 169 kind: StructureNodeKind::SymbolKind(SymbolKind::Impl),
151 detail: None, 170 detail: None,
152 deprecated: false, 171 deprecated: false,
153 }; 172 };
154 Some(node) 173 Some(node)
155 }, 174 },
156 ast::MacroRules(it) => decl(it, SymbolKind::Macro), 175 ast::MacroRules(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)),
157 _ => None, 176 _ => None,
158 } 177 }
159 } 178 }
160} 179}
161 180
181fn structure_token(token: SyntaxToken) -> Option<StructureNode> {
182 if let Some(comment) = ast::Comment::cast(token) {
183 let text = comment.text().trim();
184
185 if let Some(region_name) = text.strip_prefix("// region:").map(str::trim) {
186 return Some(StructureNode {
187 parent: None,
188 label: region_name.to_string(),
189 navigation_range: comment.syntax().text_range(),
190 node_range: comment.syntax().text_range(),
191 kind: StructureNodeKind::Region,
192 detail: None,
193 deprecated: false,
194 });
195 }
196 }
197
198 None
199}
200
162#[cfg(test)] 201#[cfg(test)]
163mod tests { 202mod tests {
164 use expect_test::{expect, Expect}; 203 use expect_test::{expect, Expect};
@@ -217,6 +256,16 @@ fn obsolete() {}
217 256
218#[deprecated(note = "for awhile")] 257#[deprecated(note = "for awhile")]
219fn very_obsolete() {} 258fn very_obsolete() {}
259
260// region: Some region name
261// endregion
262
263// region: dontpanic
264mod m {
265fn f() {}
266// endregion
267fn g() {}
268}
220"#, 269"#,
221 expect![[r#" 270 expect![[r#"
222 [ 271 [
@@ -225,7 +274,9 @@ fn very_obsolete() {}
225 label: "Foo", 274 label: "Foo",
226 navigation_range: 8..11, 275 navigation_range: 8..11,
227 node_range: 1..26, 276 node_range: 1..26,
228 kind: Struct, 277 kind: SymbolKind(
278 Struct,
279 ),
229 detail: None, 280 detail: None,
230 deprecated: false, 281 deprecated: false,
231 }, 282 },
@@ -236,7 +287,9 @@ fn very_obsolete() {}
236 label: "x", 287 label: "x",
237 navigation_range: 18..19, 288 navigation_range: 18..19,
238 node_range: 18..24, 289 node_range: 18..24,
239 kind: Field, 290 kind: SymbolKind(
291 Field,
292 ),
240 detail: Some( 293 detail: Some(
241 "i32", 294 "i32",
242 ), 295 ),
@@ -247,7 +300,9 @@ fn very_obsolete() {}
247 label: "m", 300 label: "m",
248 navigation_range: 32..33, 301 navigation_range: 32..33,
249 node_range: 28..158, 302 node_range: 28..158,
250 kind: Module, 303 kind: SymbolKind(
304 Module,
305 ),
251 detail: None, 306 detail: None,
252 deprecated: false, 307 deprecated: false,
253 }, 308 },
@@ -258,7 +313,9 @@ fn very_obsolete() {}
258 label: "bar1", 313 label: "bar1",
259 navigation_range: 43..47, 314 navigation_range: 43..47,
260 node_range: 40..52, 315 node_range: 40..52,
261 kind: Function, 316 kind: SymbolKind(
317 Function,
318 ),
262 detail: Some( 319 detail: Some(
263 "fn()", 320 "fn()",
264 ), 321 ),
@@ -271,7 +328,9 @@ fn very_obsolete() {}
271 label: "bar2", 328 label: "bar2",
272 navigation_range: 60..64, 329 navigation_range: 60..64,
273 node_range: 57..81, 330 node_range: 57..81,
274 kind: Function, 331 kind: SymbolKind(
332 Function,
333 ),
275 detail: Some( 334 detail: Some(
276 "fn<T>(t: T) -> T", 335 "fn<T>(t: T) -> T",
277 ), 336 ),
@@ -284,7 +343,9 @@ fn very_obsolete() {}
284 label: "bar3", 343 label: "bar3",
285 navigation_range: 89..93, 344 navigation_range: 89..93,
286 node_range: 86..156, 345 node_range: 86..156,
287 kind: Function, 346 kind: SymbolKind(
347 Function,
348 ),
288 detail: Some( 349 detail: Some(
289 "fn<A, B>(a: A, b: B) -> Vec< u32 >", 350 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
290 ), 351 ),
@@ -295,7 +356,9 @@ fn very_obsolete() {}
295 label: "E", 356 label: "E",
296 navigation_range: 165..166, 357 navigation_range: 165..166,
297 node_range: 160..180, 358 node_range: 160..180,
298 kind: Enum, 359 kind: SymbolKind(
360 Enum,
361 ),
299 detail: None, 362 detail: None,
300 deprecated: false, 363 deprecated: false,
301 }, 364 },
@@ -306,7 +369,9 @@ fn very_obsolete() {}
306 label: "X", 369 label: "X",
307 navigation_range: 169..170, 370 navigation_range: 169..170,
308 node_range: 169..170, 371 node_range: 169..170,
309 kind: Variant, 372 kind: SymbolKind(
373 Variant,
374 ),
310 detail: None, 375 detail: None,
311 deprecated: false, 376 deprecated: false,
312 }, 377 },
@@ -317,7 +382,9 @@ fn very_obsolete() {}
317 label: "Y", 382 label: "Y",
318 navigation_range: 172..173, 383 navigation_range: 172..173,
319 node_range: 172..178, 384 node_range: 172..178,
320 kind: Variant, 385 kind: SymbolKind(
386 Variant,
387 ),
321 detail: None, 388 detail: None,
322 deprecated: false, 389 deprecated: false,
323 }, 390 },
@@ -326,7 +393,9 @@ fn very_obsolete() {}
326 label: "T", 393 label: "T",
327 navigation_range: 186..187, 394 navigation_range: 186..187,
328 node_range: 181..193, 395 node_range: 181..193,
329 kind: TypeAlias, 396 kind: SymbolKind(
397 TypeAlias,
398 ),
330 detail: Some( 399 detail: Some(
331 "()", 400 "()",
332 ), 401 ),
@@ -337,7 +406,9 @@ fn very_obsolete() {}
337 label: "S", 406 label: "S",
338 navigation_range: 201..202, 407 navigation_range: 201..202,
339 node_range: 194..213, 408 node_range: 194..213,
340 kind: Static, 409 kind: SymbolKind(
410 Static,
411 ),
341 detail: Some( 412 detail: Some(
342 "i32", 413 "i32",
343 ), 414 ),
@@ -348,7 +419,9 @@ fn very_obsolete() {}
348 label: "C", 419 label: "C",
349 navigation_range: 220..221, 420 navigation_range: 220..221,
350 node_range: 214..232, 421 node_range: 214..232,
351 kind: Const, 422 kind: SymbolKind(
423 Const,
424 ),
352 detail: Some( 425 detail: Some(
353 "i32", 426 "i32",
354 ), 427 ),
@@ -359,7 +432,9 @@ fn very_obsolete() {}
359 label: "impl E", 432 label: "impl E",
360 navigation_range: 239..240, 433 navigation_range: 239..240,
361 node_range: 234..243, 434 node_range: 234..243,
362 kind: Impl, 435 kind: SymbolKind(
436 Impl,
437 ),
363 detail: None, 438 detail: None,
364 deprecated: false, 439 deprecated: false,
365 }, 440 },
@@ -368,7 +443,9 @@ fn very_obsolete() {}
368 label: "impl fmt::Debug for E", 443 label: "impl fmt::Debug for E",
369 navigation_range: 265..266, 444 navigation_range: 265..266,
370 node_range: 245..269, 445 node_range: 245..269,
371 kind: Impl, 446 kind: SymbolKind(
447 Impl,
448 ),
372 detail: None, 449 detail: None,
373 deprecated: false, 450 deprecated: false,
374 }, 451 },
@@ -377,7 +454,9 @@ fn very_obsolete() {}
377 label: "mc", 454 label: "mc",
378 navigation_range: 284..286, 455 navigation_range: 284..286,
379 node_range: 271..303, 456 node_range: 271..303,
380 kind: Macro, 457 kind: SymbolKind(
458 Macro,
459 ),
381 detail: None, 460 detail: None,
382 deprecated: false, 461 deprecated: false,
383 }, 462 },
@@ -386,7 +465,9 @@ fn very_obsolete() {}
386 label: "mcexp", 465 label: "mcexp",
387 navigation_range: 334..339, 466 navigation_range: 334..339,
388 node_range: 305..356, 467 node_range: 305..356,
389 kind: Macro, 468 kind: SymbolKind(
469 Macro,
470 ),
390 detail: None, 471 detail: None,
391 deprecated: false, 472 deprecated: false,
392 }, 473 },
@@ -395,7 +476,9 @@ fn very_obsolete() {}
395 label: "mcexp", 476 label: "mcexp",
396 navigation_range: 387..392, 477 navigation_range: 387..392,
397 node_range: 358..409, 478 node_range: 358..409,
398 kind: Macro, 479 kind: SymbolKind(
480 Macro,
481 ),
399 detail: None, 482 detail: None,
400 deprecated: false, 483 deprecated: false,
401 }, 484 },
@@ -404,7 +487,9 @@ fn very_obsolete() {}
404 label: "obsolete", 487 label: "obsolete",
405 navigation_range: 428..436, 488 navigation_range: 428..436,
406 node_range: 411..441, 489 node_range: 411..441,
407 kind: Function, 490 kind: SymbolKind(
491 Function,
492 ),
408 detail: Some( 493 detail: Some(
409 "fn()", 494 "fn()",
410 ), 495 ),
@@ -415,12 +500,75 @@ fn very_obsolete() {}
415 label: "very_obsolete", 500 label: "very_obsolete",
416 navigation_range: 481..494, 501 navigation_range: 481..494,
417 node_range: 443..499, 502 node_range: 443..499,
418 kind: Function, 503 kind: SymbolKind(
504 Function,
505 ),
419 detail: Some( 506 detail: Some(
420 "fn()", 507 "fn()",
421 ), 508 ),
422 deprecated: true, 509 deprecated: true,
423 }, 510 },
511 StructureNode {
512 parent: None,
513 label: "Some region name",
514 navigation_range: 501..528,
515 node_range: 501..528,
516 kind: Region,
517 detail: None,
518 deprecated: false,
519 },
520 StructureNode {
521 parent: None,
522 label: "m",
523 navigation_range: 568..569,
524 node_range: 543..606,
525 kind: SymbolKind(
526 Module,
527 ),
528 detail: None,
529 deprecated: false,
530 },
531 StructureNode {
532 parent: Some(
533 20,
534 ),
535 label: "dontpanic",
536 navigation_range: 543..563,
537 node_range: 543..563,
538 kind: Region,
539 detail: None,
540 deprecated: false,
541 },
542 StructureNode {
543 parent: Some(
544 20,
545 ),
546 label: "f",
547 navigation_range: 575..576,
548 node_range: 572..581,
549 kind: SymbolKind(
550 Function,
551 ),
552 detail: Some(
553 "fn()",
554 ),
555 deprecated: false,
556 },
557 StructureNode {
558 parent: Some(
559 20,
560 ),
561 label: "g",
562 navigation_range: 598..599,
563 node_range: 582..604,
564 kind: SymbolKind(
565 Function,
566 ),
567 detail: Some(
568 "fn()",
569 ),
570 deprecated: false,
571 },
424 ] 572 ]
425 "#]], 573 "#]],
426 ); 574 );
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index abed1969e..e8f31e4b1 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1161,4 +1161,15 @@ struct S;
1161"#, 1161"#,
1162 ) 1162 )
1163 } 1163 }
1164
1165 #[test]
1166 fn goto_incomplete_field() {
1167 check(
1168 r#"
1169struct A { a: u32 }
1170 //^
1171fn foo() { A { a$0: }; }
1172"#,
1173 )
1174 }
1164} 1175}
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 3990305fc..f4d7c14a6 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,6 +1,9 @@
1use hir::{Crate, Impl, Semantics}; 1use hir::{Impl, Semantics};
2use ide_db::RootDatabase; 2use ide_db::{
3use syntax::{algo::find_node_at_offset, ast, AstNode}; 3 defs::{Definition, NameClass, NameRefClass},
4 RootDatabase,
5};
6use syntax::{ast, AstNode};
4 7
5use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo}; 8use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo};
6 9
@@ -21,55 +24,42 @@ pub(crate) fn goto_implementation(
21 let source_file = sema.parse(position.file_id); 24 let source_file = sema.parse(position.file_id);
22 let syntax = source_file.syntax().clone(); 25 let syntax = source_file.syntax().clone();
23 26
24 let krate = sema.to_module_def(position.file_id)?.krate(); 27 let node = sema.find_node_at_offset_with_descend(&syntax, position.offset)?;
25 28 let def = match &node {
26 if let Some(nominal_def) = find_node_at_offset::<ast::Adt>(&syntax, position.offset) { 29 ast::NameLike::Name(name) => {
27 return Some(RangeInfo::new( 30 NameClass::classify(&sema, name).map(|class| class.referenced_or_defined(sema.db))
28 nominal_def.syntax().text_range(), 31 }
29 impls_for_def(&sema, &nominal_def, krate)?, 32 ast::NameLike::NameRef(name_ref) => {
30 )); 33 NameRefClass::classify(&sema, name_ref).map(|class| class.referenced(sema.db))
31 } else if let Some(trait_def) = find_node_at_offset::<ast::Trait>(&syntax, position.offset) { 34 }
32 return Some(RangeInfo::new( 35 ast::NameLike::Lifetime(_) => None,
33 trait_def.syntax().text_range(), 36 }?;
34 impls_for_trait(&sema, &trait_def, krate)?, 37 let def = match def {
35 )); 38 Definition::ModuleDef(def) => def,
36 } 39 _ => return None,
37
38 None
39}
40
41fn impls_for_def(
42 sema: &Semantics<RootDatabase>,
43 node: &ast::Adt,
44 krate: Crate,
45) -> Option<Vec<NavigationTarget>> {
46 let ty = match node {
47 ast::Adt::Struct(def) => sema.to_def(def)?.ty(sema.db),
48 ast::Adt::Enum(def) => sema.to_def(def)?.ty(sema.db),
49 ast::Adt::Union(def) => sema.to_def(def)?.ty(sema.db),
50 }; 40 };
51 41 let navs = match def {
52 let impls = Impl::all_in_crate(sema.db, krate); 42 hir::ModuleDef::Trait(trait_) => impls_for_trait(&sema, trait_),
53 43 hir::ModuleDef::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
54 Some( 44 hir::ModuleDef::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
55 impls 45 hir::ModuleDef::BuiltinType(builtin) => {
56 .into_iter() 46 let module = sema.to_module_def(position.file_id)?;
57 .filter(|impl_def| ty.is_equal_for_find_impls(&impl_def.target_ty(sema.db))) 47 impls_for_ty(&sema, builtin.ty(sema.db, module))
58 .filter_map(|imp| imp.try_to_nav(sema.db)) 48 }
59 .collect(), 49 _ => return None,
60 ) 50 };
51 Some(RangeInfo { range: node.syntax().text_range(), info: navs })
61} 52}
62 53
63fn impls_for_trait( 54fn impls_for_ty(sema: &Semantics<RootDatabase>, ty: hir::Type) -> Vec<NavigationTarget> {
64 sema: &Semantics<RootDatabase>, 55 Impl::all_for_type(sema.db, ty).into_iter().filter_map(|imp| imp.try_to_nav(sema.db)).collect()
65 node: &ast::Trait, 56}
66 krate: Crate,
67) -> Option<Vec<NavigationTarget>> {
68 let tr = sema.to_def(node)?;
69
70 let impls = Impl::for_trait(sema.db, krate, tr);
71 57
72 Some(impls.into_iter().filter_map(|imp| imp.try_to_nav(sema.db)).collect()) 58fn impls_for_trait(sema: &Semantics<RootDatabase>, trait_: hir::Trait) -> Vec<NavigationTarget> {
59 Impl::all_for_trait(sema.db, trait_)
60 .into_iter()
61 .filter_map(|imp| imp.try_to_nav(sema.db))
62 .collect()
73} 63}
74 64
75#[cfg(test)] 65#[cfg(test)]
@@ -226,4 +216,48 @@ macro Copy {}
226"#, 216"#,
227 ); 217 );
228 } 218 }
219
220 #[test]
221 fn goto_implementation_type_alias() {
222 check(
223 r#"
224struct Foo;
225
226type Bar$0 = Foo;
227
228impl Foo {}
229 //^^^
230impl Bar {}
231 //^^^
232"#,
233 );
234 }
235
236 #[test]
237 fn goto_implementation_adt_generic() {
238 check(
239 r#"
240struct Foo$0<T>;
241
242impl<T> Foo<T> {}
243 //^^^^^^
244impl Foo<str> {}
245 //^^^^^^^^
246"#,
247 );
248 }
249
250 #[test]
251 fn goto_implementation_builtin() {
252 check(
253 r#"
254//- /lib.rs crate:main deps:core
255fn foo(_: bool$0) {{}}
256//- /libcore.rs crate:core
257#[lang = "bool"]
258impl bool {}
259 //^^^^
260"#,
261 );
262 }
229} 263}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 4ceb20742..16c04eeee 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -219,7 +219,7 @@ fn hint_iterator(
219 let strukt = std::iter::successors(Some(ty.clone()), |ty| ty.remove_ref()) 219 let strukt = std::iter::successors(Some(ty.clone()), |ty| ty.remove_ref())
220 .last() 220 .last()
221 .and_then(|strukt| strukt.as_adt())?; 221 .and_then(|strukt| strukt.as_adt())?;
222 let krate = strukt.krate(db)?; 222 let krate = strukt.krate(db);
223 if krate != famous_defs.core()? { 223 if krate != famous_defs.core()? {
224 return None; 224 return None;
225 } 225 }
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 20a920ddb..d571ed559 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -218,7 +218,7 @@ mod tests {
218 let result = join_lines(&file, range); 218 let result = join_lines(&file, range);
219 219
220 let actual = { 220 let actual = {
221 let mut actual = before.to_string(); 221 let mut actual = before;
222 result.apply(&mut actual); 222 result.apply(&mut actual);
223 actual 223 actual
224 }; 224 };
@@ -622,7 +622,7 @@ fn foo() {
622 let parse = SourceFile::parse(&before); 622 let parse = SourceFile::parse(&before);
623 let result = join_lines(&parse.tree(), sel); 623 let result = join_lines(&parse.tree(), sel);
624 let actual = { 624 let actual = {
625 let mut actual = before.to_string(); 625 let mut actual = before;
626 result.apply(&mut actual); 626 result.apply(&mut actual);
627 actual 627 actual
628 }; 628 };
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index d1a250d48..662da5a96 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -71,7 +71,7 @@ pub use crate::{
71 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, 71 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
72 display::navigation_target::NavigationTarget, 72 display::navigation_target::NavigationTarget,
73 expand_macro::ExpandedMacro, 73 expand_macro::ExpandedMacro,
74 file_structure::StructureNode, 74 file_structure::{StructureNode, StructureNodeKind},
75 folding_ranges::{Fold, FoldKind}, 75 folding_ranges::{Fold, FoldKind},
76 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, 76 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
77 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 77 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
@@ -87,7 +87,7 @@ pub use crate::{
87pub use hir::{Documentation, Semantics}; 87pub use hir::{Documentation, Semantics};
88pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; 88pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind};
89pub use ide_completion::{ 89pub use ide_completion::{
90 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, 90 CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit,
91 InsertTextFormat, 91 InsertTextFormat,
92}; 92};
93pub use ide_db::{ 93pub use ide_db::{
@@ -101,7 +101,7 @@ pub use ide_db::{
101 search::{ReferenceAccess, SearchScope}, 101 search::{ReferenceAccess, SearchScope},
102 source_change::{FileSystemEdit, SourceChange}, 102 source_change::{FileSystemEdit, SourceChange},
103 symbol_index::Query, 103 symbol_index::Query,
104 RootDatabase, 104 RootDatabase, SymbolKind,
105}; 105};
106pub use ide_ssr::SsrError; 106pub use ide_ssr::SsrError;
107pub use syntax::{TextRange, TextSize}; 107pub use syntax::{TextRange, TextSize};
@@ -447,6 +447,15 @@ impl Analysis {
447 self.with_db(|db| runnables::runnables(db, file_id)) 447 self.with_db(|db| runnables::runnables(db, file_id))
448 } 448 }
449 449
450 /// Returns the set of tests for the given file position.
451 pub fn related_tests(
452 &self,
453 position: FilePosition,
454 search_scope: Option<SearchScope>,
455 ) -> Cancelable<Vec<Runnable>> {
456 self.with_db(|db| runnables::related_tests(db, position, search_scope))
457 }
458
450 /// Computes syntax highlighting for the given file 459 /// Computes syntax highlighting for the given file
451 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> { 460 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> {
452 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) 461 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index 03d71b380..22b0d6ecb 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -1,6 +1,7 @@
1use hir::Semantics; 1use hir::Semantics;
2use ide_db::base_db::{CrateId, FileId, FilePosition}; 2use ide_db::base_db::{CrateId, FileId, FilePosition};
3use ide_db::RootDatabase; 3use ide_db::RootDatabase;
4use itertools::Itertools;
4use syntax::{ 5use syntax::{
5 algo::find_node_at_offset, 6 algo::find_node_at_offset,
6 ast::{self, AstNode}, 7 ast::{self, AstNode},
@@ -18,8 +19,7 @@ use crate::NavigationTarget;
18// | VS Code | **Rust Analyzer: Locate parent module** 19// | VS Code | **Rust Analyzer: Locate parent module**
19// |=== 20// |===
20 21
21/// This returns `Vec` because a module may be included from several places. We 22/// This returns `Vec` because a module may be included from several places.
22/// don't handle this case yet though, so the Vec has length at most one.
23pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { 23pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
24 let sema = Semantics::new(db); 24 let sema = Semantics::new(db);
25 let source_file = sema.parse(position.file_id); 25 let source_file = sema.parse(position.file_id);
@@ -37,27 +37,23 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na
37 } 37 }
38 } 38 }
39 39
40 let module = match module { 40 match module {
41 Some(module) => sema.to_def(&module), 41 Some(module) => sema
42 None => sema.to_module_def(position.file_id), 42 .to_def(&module)
43 }; 43 .into_iter()
44 let module = match module { 44 .map(|module| NavigationTarget::from_module_to_decl(db, module))
45 None => return Vec::new(), 45 .collect(),
46 Some(it) => it, 46 None => sema
47 }; 47 .to_module_defs(position.file_id)
48 let nav = NavigationTarget::from_module_to_decl(db, module); 48 .map(|module| NavigationTarget::from_module_to_decl(db, module))
49 vec![nav] 49 .collect(),
50 }
50} 51}
51 52
52/// Returns `Vec` for the same reason as `parent_module` 53/// Returns `Vec` for the same reason as `parent_module`
53pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> { 54pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
54 let sema = Semantics::new(db); 55 let sema = Semantics::new(db);
55 let module = match sema.to_module_def(file_id) { 56 sema.to_module_defs(file_id).map(|module| module.krate().into()).unique().collect()
56 Some(it) => it,
57 None => return Vec::new(),
58 };
59 let krate = module.krate();
60 vec![krate.into()]
61} 57}
62 58
63#[cfg(test)] 59#[cfg(test)]
@@ -67,11 +63,13 @@ mod tests {
67 use crate::fixture; 63 use crate::fixture;
68 64
69 fn check(ra_fixture: &str) { 65 fn check(ra_fixture: &str) {
70 let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture); 66 let (analysis, position, expected) = fixture::annotations(ra_fixture);
71 let mut navs = analysis.parent_module(position).unwrap(); 67 let navs = analysis.parent_module(position).unwrap();
72 assert_eq!(navs.len(), 1); 68 let navs = navs
73 let nav = navs.pop().unwrap(); 69 .iter()
74 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); 70 .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
71 .collect::<Vec<_>>();
72 assert_eq!(expected.into_iter().map(|(fr, _)| fr).collect::<Vec<_>>(), navs);
75 } 73 }
76 74
77 #[test] 75 #[test]
@@ -120,15 +118,46 @@ mod foo {
120 } 118 }
121 119
122 #[test] 120 #[test]
123 fn test_resolve_crate_root() { 121 fn test_resolve_multi_parent_module() {
124 let (analysis, file_id) = fixture::file( 122 check(
125 r#" 123 r#"
126//- /main.rs 124//- /main.rs
127mod foo; 125mod foo;
126 //^^^
127#[path = "foo.rs"]
128mod bar;
129 //^^^
130//- /foo.rs
131$0
132"#,
133 );
134 }
135
136 #[test]
137 fn test_resolve_crate_root() {
138 let (analysis, file_id) = fixture::file(
139 r#"
128//- /foo.rs 140//- /foo.rs
129$0 141$0
142//- /main.rs
143mod foo;
130"#, 144"#,
131 ); 145 );
132 assert_eq!(analysis.crate_for(file_id).unwrap().len(), 1); 146 assert_eq!(analysis.crate_for(file_id).unwrap().len(), 1);
133 } 147 }
148
149 #[test]
150 fn test_resolve_multi_parent_crate() {
151 let (analysis, file_id) = fixture::file(
152 r#"
153//- /baz.rs
154$0
155//- /foo.rs crate:foo
156mod baz;
157//- /bar.rs crate:bar
158mod baz;
159"#,
160 );
161 assert_eq!(analysis.crate_for(file_id).unwrap().len(), 2);
162 }
134} 163}
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index fef70533d..ec7c7686d 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -29,7 +29,7 @@ use crate::{display::TryToNav, FilePosition, NavigationTarget};
29 29
30#[derive(Debug, Clone)] 30#[derive(Debug, Clone)]
31pub struct ReferenceSearchResult { 31pub struct ReferenceSearchResult {
32 pub declaration: Declaration, 32 pub declaration: Option<Declaration>,
33 pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceAccess>)>>, 33 pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceAccess>)>>,
34} 34}
35 35
@@ -91,10 +91,10 @@ pub(crate) fn find_all_refs(
91 _ => {} 91 _ => {}
92 } 92 }
93 } 93 }
94 let nav = def.try_to_nav(sema.db)?; 94 let declaration = def.try_to_nav(sema.db).map(|nav| {
95 let decl_range = nav.focus_or_full_range(); 95 let decl_range = nav.focus_or_full_range();
96 96 Declaration { nav, access: decl_access(&def, &syntax, decl_range) }
97 let declaration = Declaration { nav, access: decl_access(&def, &syntax, decl_range) }; 97 });
98 let references = usages 98 let references = usages
99 .into_iter() 99 .into_iter()
100 .map(|(file_id, refs)| { 100 .map(|(file_id, refs)| {
@@ -1004,8 +1004,7 @@ impl Foo {
1004 let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap(); 1004 let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
1005 1005
1006 let mut actual = String::new(); 1006 let mut actual = String::new();
1007 { 1007 if let Some(decl) = refs.declaration {
1008 let decl = refs.declaration;
1009 format_to!(actual, "{}", decl.nav.debug_render()); 1008 format_to!(actual, "{}", decl.nav.debug_render());
1010 if let Some(access) = decl.access { 1009 if let Some(access) = decl.access {
1011 format_to!(actual, " {:?}", access) 1010 format_to!(actual, " {:?}", access)
@@ -1258,4 +1257,17 @@ fn main() {
1258 "#]], 1257 "#]],
1259 ); 1258 );
1260 } 1259 }
1260
1261 #[test]
1262 fn test_primitives() {
1263 check(
1264 r#"
1265fn foo(_: bool) -> bo$0ol { true }
1266"#,
1267 expect![[r#"
1268 FileId(0) 10..14
1269 FileId(0) 19..23
1270 "#]],
1271 );
1272 }
1261} 1273}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 1e378279d..5340b638a 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -510,7 +510,8 @@ fn source_edit_from_def(
510 def: Definition, 510 def: Definition,
511 new_name: &str, 511 new_name: &str,
512) -> RenameResult<(FileId, TextEdit)> { 512) -> RenameResult<(FileId, TextEdit)> {
513 let nav = def.try_to_nav(sema.db).unwrap(); 513 let nav =
514 def.try_to_nav(sema.db).ok_or_else(|| format_err!("No references found at position"))?;
514 515
515 let mut replacement_text = String::new(); 516 let mut replacement_text = String::new();
516 let mut repl_range = nav.focus_or_full_range(); 517 let mut repl_range = nav.focus_or_full_range();
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 280565563..0c7a8fbf8 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -1,10 +1,19 @@
1use std::fmt; 1use std::fmt;
2 2
3use ast::NameOwner;
3use cfg::CfgExpr; 4use cfg::CfgExpr;
4use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; 5use either::Either;
6use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
5use ide_assists::utils::test_related_attribute; 7use ide_assists::utils::test_related_attribute;
6use ide_db::{defs::Definition, RootDatabase, SymbolKind}; 8use ide_db::{
9 base_db::{FilePosition, FileRange},
10 defs::Definition,
11 helpers::visit_file_defs,
12 search::SearchScope,
13 RootDatabase, SymbolKind,
14};
7use itertools::Itertools; 15use itertools::Itertools;
16use rustc_hash::FxHashSet;
8use syntax::{ 17use syntax::{
9 ast::{self, AstNode, AttrsOwner}, 18 ast::{self, AstNode, AttrsOwner},
10 match_ast, SyntaxNode, 19 match_ast, SyntaxNode,
@@ -12,17 +21,17 @@ use syntax::{
12 21
13use crate::{ 22use crate::{
14 display::{ToNav, TryToNav}, 23 display::{ToNav, TryToNav},
15 FileId, NavigationTarget, 24 references, FileId, NavigationTarget,
16}; 25};
17 26
18#[derive(Debug, Clone)] 27#[derive(Debug, Clone, Hash, PartialEq, Eq)]
19pub struct Runnable { 28pub struct Runnable {
20 pub nav: NavigationTarget, 29 pub nav: NavigationTarget,
21 pub kind: RunnableKind, 30 pub kind: RunnableKind,
22 pub cfg: Option<CfgExpr>, 31 pub cfg: Option<CfgExpr>,
23} 32}
24 33
25#[derive(Debug, Clone)] 34#[derive(Debug, Clone, Hash, PartialEq, Eq)]
26pub enum TestId { 35pub enum TestId {
27 Name(String), 36 Name(String),
28 Path(String), 37 Path(String),
@@ -37,7 +46,7 @@ impl fmt::Display for TestId {
37 } 46 }
38} 47}
39 48
40#[derive(Debug, Clone)] 49#[derive(Debug, Clone, Hash, PartialEq, Eq)]
41pub enum RunnableKind { 50pub enum RunnableKind {
42 Test { test_id: TestId, attr: TestAttr }, 51 Test { test_id: TestId, attr: TestAttr },
43 TestMod { path: String }, 52 TestMod { path: String },
@@ -95,49 +104,129 @@ impl Runnable {
95// |=== 104// |===
96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 105pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97 let sema = Semantics::new(db); 106 let sema = Semantics::new(db);
98 let module = match sema.to_module_def(file_id) {
99 None => return Vec::new(),
100 Some(it) => it,
101 };
102 107
103 let mut res = Vec::new(); 108 let mut res = Vec::new();
104 runnables_mod(&sema, &mut res, module); 109 visit_file_defs(&sema, file_id, &mut |def| match def {
110 Either::Left(def) => {
111 let runnable = match def {
112 hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
113 hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
114 _ => None,
115 };
116 res.extend(runnable.or_else(|| module_def_doctest(&sema, def)))
117 }
118 Either::Right(impl_) => {
119 res.extend(impl_.items(db).into_iter().filter_map(|assoc| match assoc {
120 hir::AssocItem::Function(it) => {
121 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
122 }
123 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
124 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
125 }))
126 }
127 });
105 res 128 res
106} 129}
107 130
108fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) { 131// Feature: Related Tests
109 acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| { 132//
110 let runnable = match def { 133// Provides a sneak peek of all tests where the current item is used.
111 hir::ModuleDef::Module(it) => runnable_mod(&sema, it), 134//
112 hir::ModuleDef::Function(it) => runnable_fn(&sema, it), 135// The simplest way to use this feature is via the context menu:
113 _ => None, 136// - Right-click on the selected item. The context menu opens.
114 }; 137// - Select **Peek related tests**
115 runnable.or_else(|| module_def_doctest(&sema, def)) 138//
116 })); 139// |===
140// | Editor | Action Name
141//
142// | VS Code | **Rust Analyzer: Peek related tests**
143// |===
144pub(crate) fn related_tests(
145 db: &RootDatabase,
146 position: FilePosition,
147 search_scope: Option<SearchScope>,
148) -> Vec<Runnable> {
149 let sema = Semantics::new(db);
150 let mut res: FxHashSet<Runnable> = FxHashSet::default();
117 151
118 acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map( 152 find_related_tests(&sema, position, search_scope, &mut res);
119 |def| match def { 153
120 hir::AssocItem::Function(it) => { 154 res.into_iter().collect_vec()
121 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into())) 155}
122 } 156
123 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()), 157fn find_related_tests(
124 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()), 158 sema: &Semantics<RootDatabase>,
125 }, 159 position: FilePosition,
126 )); 160 search_scope: Option<SearchScope>,
127 161 tests: &mut FxHashSet<Runnable>,
128 for def in module.declarations(sema.db) { 162) {
129 if let hir::ModuleDef::Module(submodule) = def { 163 if let Some(refs) = references::find_all_refs(&sema, position, search_scope) {
130 match submodule.definition_source(sema.db).value { 164 for (file_id, refs) in refs.references {
131 hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule), 165 let file = sema.parse(file_id);
132 hir::ModuleSource::SourceFile(_) => { 166 let file = file.syntax();
133 cov_mark::hit!(dont_recurse_in_outline_submodules) 167 let functions = refs.iter().filter_map(|(range, _)| {
168 let token = file.token_at_offset(range.start()).next()?;
169 let token = sema.descend_into_macros(token);
170 let syntax = token.parent();
171 syntax.ancestors().find_map(ast::Fn::cast)
172 });
173
174 for fn_def in functions {
175 if let Some(runnable) = as_test_runnable(&sema, &fn_def) {
176 // direct test
177 tests.insert(runnable);
178 } else if let Some(module) = parent_test_module(&sema, &fn_def) {
179 // indirect test
180 find_related_tests_in_module(sema, &fn_def, &module, tests);
134 } 181 }
135 hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable
136 } 182 }
137 } 183 }
138 } 184 }
139} 185}
140 186
187fn find_related_tests_in_module(
188 sema: &Semantics<RootDatabase>,
189 fn_def: &ast::Fn,
190 parent_module: &hir::Module,
191 tests: &mut FxHashSet<Runnable>,
192) {
193 if let Some(fn_name) = fn_def.name() {
194 let mod_source = parent_module.definition_source(sema.db);
195 let range = match mod_source.value {
196 hir::ModuleSource::Module(m) => m.syntax().text_range(),
197 hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
198 hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
199 };
200
201 let file_id = mod_source.file_id.original_file(sema.db);
202 let mod_scope = SearchScope::file_range(FileRange { file_id, range });
203 let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
204 find_related_tests(sema, fn_pos, Some(mod_scope), tests)
205 }
206}
207
208fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
209 if test_related_attribute(&fn_def).is_some() {
210 let function = sema.to_def(fn_def)?;
211 runnable_fn(sema, function)
212 } else {
213 None
214 }
215}
216
217fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
218 fn_def.syntax().ancestors().find_map(|node| {
219 let module = ast::Module::cast(node)?;
220 let module = sema.to_def(&module)?;
221
222 if has_test_function_or_multiple_test_submodules(sema, &module) {
223 Some(module)
224 } else {
225 None
226 }
227 })
228}
229
141pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { 230pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
142 let func = def.source(sema.db)?; 231 let func = def.source(sema.db)?;
143 let name_string = def.name(sema.db).to_string(); 232 let name_string = def.name(sema.db).to_string();
@@ -234,11 +323,21 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op
234 // FIXME: this also looks very wrong 323 // FIXME: this also looks very wrong
235 if let Some(assoc_def) = assoc_def { 324 if let Some(assoc_def) = assoc_def {
236 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) { 325 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
237 if let Some(adt) = imp.target_ty(sema.db).as_adt() { 326 let ty = imp.target_ty(sema.db);
238 let name = adt.name(sema.db).to_string(); 327 if let Some(adt) = ty.as_adt() {
328 let name = adt.name(sema.db);
239 let idx = path.rfind(':').map_or(0, |idx| idx + 1); 329 let idx = path.rfind(':').map_or(0, |idx| idx + 1);
240 let (prefix, suffix) = path.split_at(idx); 330 let (prefix, suffix) = path.split_at(idx);
241 return format!("{}{}::{}", prefix, name, suffix); 331 let mut ty_params = ty.type_parameters().peekable();
332 let params = if ty_params.peek().is_some() {
333 format!(
334 "<{}>",
335 ty_params.format_with(", ", |ty, cb| cb(&ty.display(sema.db)))
336 )
337 } else {
338 String::new()
339 };
340 return format!("{}{}{}::{}", prefix, name, params, suffix);
242 } 341 }
243 } 342 }
244 } 343 }
@@ -256,7 +355,7 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op
256 Some(res) 355 Some(res)
257} 356}
258 357
259#[derive(Debug, Copy, Clone)] 358#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
260pub struct TestAttr { 359pub struct TestAttr {
261 pub ignore: bool, 360 pub ignore: bool,
262} 361}
@@ -349,6 +448,12 @@ mod tests {
349 ); 448 );
350 } 449 }
351 450
451 fn check_tests(ra_fixture: &str, expect: Expect) {
452 let (analysis, position) = fixture::position(ra_fixture);
453 let tests = analysis.related_tests(position, None).unwrap();
454 expect.assert_debug_eq(&tests);
455 }
456
352 #[test] 457 #[test]
353 fn test_runnables() { 458 fn test_runnables() {
354 check( 459 check(
@@ -1056,7 +1161,6 @@ mod tests {
1056 1161
1057 #[test] 1162 #[test]
1058 fn dont_recurse_in_outline_submodules() { 1163 fn dont_recurse_in_outline_submodules() {
1059 cov_mark::check!(dont_recurse_in_outline_submodules);
1060 check( 1164 check(
1061 r#" 1165 r#"
1062//- /lib.rs 1166//- /lib.rs
@@ -1074,4 +1178,261 @@ mod tests {
1074 "#]], 1178 "#]],
1075 ); 1179 );
1076 } 1180 }
1181
1182 #[test]
1183 fn find_no_tests() {
1184 check_tests(
1185 r#"
1186//- /lib.rs
1187fn foo$0() { };
1188"#,
1189 expect![[r#"
1190 []
1191 "#]],
1192 );
1193 }
1194
1195 #[test]
1196 fn find_direct_fn_test() {
1197 check_tests(
1198 r#"
1199//- /lib.rs
1200fn foo$0() { };
1201
1202mod tests {
1203 #[test]
1204 fn foo_test() {
1205 super::foo()
1206 }
1207}
1208"#,
1209 expect![[r#"
1210 [
1211 Runnable {
1212 nav: NavigationTarget {
1213 file_id: FileId(
1214 0,
1215 ),
1216 full_range: 31..85,
1217 focus_range: 46..54,
1218 name: "foo_test",
1219 kind: Function,
1220 },
1221 kind: Test {
1222 test_id: Path(
1223 "tests::foo_test",
1224 ),
1225 attr: TestAttr {
1226 ignore: false,
1227 },
1228 },
1229 cfg: None,
1230 },
1231 ]
1232 "#]],
1233 );
1234 }
1235
1236 #[test]
1237 fn find_direct_struct_test() {
1238 check_tests(
1239 r#"
1240//- /lib.rs
1241struct Fo$0o;
1242fn foo(arg: &Foo) { };
1243
1244mod tests {
1245 use super::*;
1246
1247 #[test]
1248 fn foo_test() {
1249 foo(Foo);
1250 }
1251}
1252"#,
1253 expect![[r#"
1254 [
1255 Runnable {
1256 nav: NavigationTarget {
1257 file_id: FileId(
1258 0,
1259 ),
1260 full_range: 71..122,
1261 focus_range: 86..94,
1262 name: "foo_test",
1263 kind: Function,
1264 },
1265 kind: Test {
1266 test_id: Path(
1267 "tests::foo_test",
1268 ),
1269 attr: TestAttr {
1270 ignore: false,
1271 },
1272 },
1273 cfg: None,
1274 },
1275 ]
1276 "#]],
1277 );
1278 }
1279
1280 #[test]
1281 fn find_indirect_fn_test() {
1282 check_tests(
1283 r#"
1284//- /lib.rs
1285fn foo$0() { };
1286
1287mod tests {
1288 use super::foo;
1289
1290 fn check1() {
1291 check2()
1292 }
1293
1294 fn check2() {
1295 foo()
1296 }
1297
1298 #[test]
1299 fn foo_test() {
1300 check1()
1301 }
1302}
1303"#,
1304 expect![[r#"
1305 [
1306 Runnable {
1307 nav: NavigationTarget {
1308 file_id: FileId(
1309 0,
1310 ),
1311 full_range: 133..183,
1312 focus_range: 148..156,
1313 name: "foo_test",
1314 kind: Function,
1315 },
1316 kind: Test {
1317 test_id: Path(
1318 "tests::foo_test",
1319 ),
1320 attr: TestAttr {
1321 ignore: false,
1322 },
1323 },
1324 cfg: None,
1325 },
1326 ]
1327 "#]],
1328 );
1329 }
1330
1331 #[test]
1332 fn tests_are_unique() {
1333 check_tests(
1334 r#"
1335//- /lib.rs
1336fn foo$0() { };
1337
1338mod tests {
1339 use super::foo;
1340
1341 #[test]
1342 fn foo_test() {
1343 foo();
1344 foo();
1345 }
1346
1347 #[test]
1348 fn foo2_test() {
1349 foo();
1350 foo();
1351 }
1352
1353}
1354"#,
1355 expect![[r#"
1356 [
1357 Runnable {
1358 nav: NavigationTarget {
1359 file_id: FileId(
1360 0,
1361 ),
1362 full_range: 52..115,
1363 focus_range: 67..75,
1364 name: "foo_test",
1365 kind: Function,
1366 },
1367 kind: Test {
1368 test_id: Path(
1369 "tests::foo_test",
1370 ),
1371 attr: TestAttr {
1372 ignore: false,
1373 },
1374 },
1375 cfg: None,
1376 },
1377 Runnable {
1378 nav: NavigationTarget {
1379 file_id: FileId(
1380 0,
1381 ),
1382 full_range: 121..185,
1383 focus_range: 136..145,
1384 name: "foo2_test",
1385 kind: Function,
1386 },
1387 kind: Test {
1388 test_id: Path(
1389 "tests::foo2_test",
1390 ),
1391 attr: TestAttr {
1392 ignore: false,
1393 },
1394 },
1395 cfg: None,
1396 },
1397 ]
1398 "#]],
1399 );
1400 }
1401
1402 #[test]
1403 fn doc_test_type_params() {
1404 check(
1405 r#"
1406//- /lib.rs
1407$0
1408struct Foo<T, U>;
1409
1410impl<T, U> Foo<T, U> {
1411 /// ```rust
1412 /// ````
1413 fn t() {}
1414}
1415"#,
1416 &[&DOCTEST],
1417 expect![[r#"
1418 [
1419 Runnable {
1420 nav: NavigationTarget {
1421 file_id: FileId(
1422 0,
1423 ),
1424 full_range: 47..85,
1425 name: "t",
1426 },
1427 kind: DocTest {
1428 test_id: Path(
1429 "Foo<T, U>::t",
1430 ),
1431 },
1432 cfg: None,
1433 },
1434 ]
1435 "#]],
1436 );
1437 }
1077} 1438}
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index e3c3aebac..a718faf63 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -145,9 +145,8 @@ mod tests {
145 use super::*; 145 use super::*;
146 146
147 fn do_type_char(char_typed: char, before: &str) -> Option<String> { 147 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
148 let (offset, before) = extract_offset(before); 148 let (offset, mut before) = extract_offset(before);
149 let edit = TextEdit::insert(offset, char_typed.to_string()); 149 let edit = TextEdit::insert(offset, char_typed.to_string());
150 let mut before = before.to_string();
151 edit.apply(&mut before); 150 edit.apply(&mut before);
152 let parse = SourceFile::parse(&before); 151 let parse = SourceFile::parse(&before);
153 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 152 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {