diff options
Diffstat (limited to 'crates')
20 files changed, 740 insertions, 89 deletions
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs index 4e252edf0..0b581dc22 100644 --- a/crates/assists/src/handlers/remove_dbg.rs +++ b/crates/assists/src/handlers/remove_dbg.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode}, |
3 | TextRange, TextSize, T, | 3 | SyntaxElement, TextRange, TextSize, T, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
@@ -22,62 +22,118 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
22 | // ``` | 22 | // ``` |
23 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 23 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | 24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; |
25 | let new_contents = adjusted_macro_contents(¯o_call)?; | ||
25 | 26 | ||
26 | if !is_valid_macrocall(¯o_call, "dbg")? { | 27 | let macro_text_range = macro_call.syntax().text_range(); |
27 | return None; | ||
28 | } | ||
29 | |||
30 | let is_leaf = macro_call.syntax().next_sibling().is_none(); | ||
31 | |||
32 | let macro_end = if macro_call.semicolon_token().is_some() { | 28 | let macro_end = if macro_call.semicolon_token().is_some() { |
33 | macro_call.syntax().text_range().end() - TextSize::of(';') | 29 | macro_text_range.end() - TextSize::of(';') |
34 | } else { | 30 | } else { |
35 | macro_call.syntax().text_range().end() | 31 | macro_text_range.end() |
36 | }; | 32 | }; |
37 | 33 | ||
38 | // macro_range determines what will be deleted and replaced with macro_content | 34 | acc.add( |
39 | let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); | 35 | AssistId("remove_dbg", AssistKind::Refactor), |
40 | let paste_instead_of_dbg = { | 36 | "Remove dbg!()", |
41 | let text = macro_call.token_tree()?.syntax().text(); | 37 | macro_text_range, |
42 | 38 | |builder| { | |
43 | // leafiness determines if we should include the parenthesis or not | 39 | builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents); |
44 | let slice_index: TextRange = if is_leaf { | 40 | }, |
45 | // leaf means - we can extract the contents of the dbg! in text | 41 | ) |
46 | TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) | 42 | } |
47 | } else { | ||
48 | // not leaf - means we should keep the parens | ||
49 | TextRange::up_to(text.len()) | ||
50 | }; | ||
51 | text.slice(slice_index).to_string() | ||
52 | }; | ||
53 | 43 | ||
54 | let target = macro_call.syntax().text_range(); | 44 | fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { |
55 | acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { | 45 | let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; |
56 | builder.replace(macro_range, paste_instead_of_dbg); | 46 | let is_leaf = macro_call.syntax().next_sibling().is_none(); |
57 | }) | 47 | let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); |
48 | let slice_index = if is_leaf || !needs_parentheses_around_macro_contents(contents) { | ||
49 | TextRange::new(TextSize::of('('), macro_text_with_brackets.len() - TextSize::of(')')) | ||
50 | } else { | ||
51 | // leave parenthesis around macro contents to preserve the semantics | ||
52 | TextRange::up_to(macro_text_with_brackets.len()) | ||
53 | }; | ||
54 | Some(macro_text_with_brackets.slice(slice_index).to_string()) | ||
58 | } | 55 | } |
59 | 56 | ||
60 | /// Verifies that the given macro_call actually matches the given name | 57 | /// Verifies that the given macro_call actually matches the given name |
61 | /// and contains proper ending tokens | 58 | /// and contains proper ending tokens, then returns the contents between the ending tokens |
62 | fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { | 59 | fn get_valid_macrocall_contents( |
60 | macro_call: &ast::MacroCall, | ||
61 | macro_name: &str, | ||
62 | ) -> Option<Vec<SyntaxElement>> { | ||
63 | let path = macro_call.path()?; | 63 | let path = macro_call.path()?; |
64 | let name_ref = path.segment()?.name_ref()?; | 64 | let name_ref = path.segment()?.name_ref()?; |
65 | 65 | ||
66 | // Make sure it is actually a dbg-macro call, dbg followed by ! | 66 | // Make sure it is actually a dbg-macro call, dbg followed by ! |
67 | let excl = path.syntax().next_sibling_or_token()?; | 67 | let excl = path.syntax().next_sibling_or_token()?; |
68 | |||
69 | if name_ref.text() != macro_name || excl.kind() != T![!] { | 68 | if name_ref.text() != macro_name || excl.kind() != T![!] { |
70 | return None; | 69 | return None; |
71 | } | 70 | } |
72 | 71 | ||
73 | let node = macro_call.token_tree()?.syntax().clone(); | 72 | let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens(); |
74 | let first_child = node.first_child_or_token()?; | 73 | let first_child = children_with_tokens.next()?; |
75 | let last_child = node.last_child_or_token()?; | 74 | let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); |
75 | let last_child = contents_between_brackets.pop()?; | ||
76 | |||
77 | if contents_between_brackets.is_empty() { | ||
78 | None | ||
79 | } else { | ||
80 | match (first_child.kind(), last_child.kind()) { | ||
81 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { | ||
82 | Some(contents_between_brackets) | ||
83 | } | ||
84 | _ => None, | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | |||
89 | fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool { | ||
90 | if macro_contents.len() < 2 { | ||
91 | return false; | ||
92 | } | ||
93 | |||
94 | let mut macro_contents_kind_not_in_brackets = Vec::with_capacity(macro_contents.len()); | ||
76 | 95 | ||
77 | match (first_child.kind(), last_child.kind()) { | 96 | let mut first_bracket_in_macro = None; |
78 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), | 97 | let mut unpaired_brackets_in_contents = Vec::new(); |
79 | _ => Some(false), | 98 | for element in macro_contents { |
99 | match element.kind() { | ||
100 | T!['('] | T!['['] | T!['{'] => { | ||
101 | if let None = first_bracket_in_macro { | ||
102 | first_bracket_in_macro = Some(element.clone()) | ||
103 | } | ||
104 | unpaired_brackets_in_contents.push(element); | ||
105 | } | ||
106 | T![')'] => { | ||
107 | if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['(']) | ||
108 | { | ||
109 | return true; | ||
110 | } | ||
111 | } | ||
112 | T![']'] => { | ||
113 | if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['[']) | ||
114 | { | ||
115 | return true; | ||
116 | } | ||
117 | } | ||
118 | T!['}'] => { | ||
119 | if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{']) | ||
120 | { | ||
121 | return true; | ||
122 | } | ||
123 | } | ||
124 | other_kind => { | ||
125 | if unpaired_brackets_in_contents.is_empty() { | ||
126 | macro_contents_kind_not_in_brackets.push(other_kind); | ||
127 | } | ||
128 | } | ||
129 | } | ||
80 | } | 130 | } |
131 | |||
132 | !unpaired_brackets_in_contents.is_empty() | ||
133 | || matches!(first_bracket_in_macro, Some(bracket) if bracket.kind() != T!['(']) | ||
134 | || macro_contents_kind_not_in_brackets | ||
135 | .into_iter() | ||
136 | .any(|macro_contents_kind| macro_contents_kind.is_punct()) | ||
81 | } | 137 | } |
82 | 138 | ||
83 | #[cfg(test)] | 139 | #[cfg(test)] |
@@ -157,12 +213,37 @@ fn foo(n: usize) { | |||
157 | } | 213 | } |
158 | 214 | ||
159 | #[test] | 215 | #[test] |
216 | fn remove_dbg_from_non_leaf_simple_expression() { | ||
217 | check_assist( | ||
218 | remove_dbg, | ||
219 | " | ||
220 | fn main() { | ||
221 | let mut a = 1; | ||
222 | while dbg!<|>(a) < 10000 { | ||
223 | a += 1; | ||
224 | } | ||
225 | } | ||
226 | ", | ||
227 | " | ||
228 | fn main() { | ||
229 | let mut a = 1; | ||
230 | while a < 10000 { | ||
231 | a += 1; | ||
232 | } | ||
233 | } | ||
234 | ", | ||
235 | ); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
160 | fn test_remove_dbg_keep_expression() { | 239 | fn test_remove_dbg_keep_expression() { |
161 | check_assist( | 240 | check_assist( |
162 | remove_dbg, | 241 | remove_dbg, |
163 | r#"let res = <|>dbg!(a + b).foo();"#, | 242 | r#"let res = <|>dbg!(a + b).foo();"#, |
164 | r#"let res = (a + b).foo();"#, | 243 | r#"let res = (a + b).foo();"#, |
165 | ); | 244 | ); |
245 | |||
246 | check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#); | ||
166 | } | 247 | } |
167 | 248 | ||
168 | #[test] | 249 | #[test] |
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs index f3d65cdf0..9a61f1d56 100644 --- a/crates/base_db/src/input.rs +++ b/crates/base_db/src/input.rs | |||
@@ -12,7 +12,7 @@ use cfg::CfgOptions; | |||
12 | use rustc_hash::{FxHashMap, FxHashSet}; | 12 | use rustc_hash::{FxHashMap, FxHashSet}; |
13 | use syntax::SmolStr; | 13 | use syntax::SmolStr; |
14 | use tt::TokenExpander; | 14 | use tt::TokenExpander; |
15 | use vfs::file_set::FileSet; | 15 | use vfs::{file_set::FileSet, VfsPath}; |
16 | 16 | ||
17 | pub use vfs::FileId; | 17 | pub use vfs::FileId; |
18 | 18 | ||
@@ -43,6 +43,12 @@ impl SourceRoot { | |||
43 | pub fn new_library(file_set: FileSet) -> SourceRoot { | 43 | pub fn new_library(file_set: FileSet) -> SourceRoot { |
44 | SourceRoot { is_library: true, file_set } | 44 | SourceRoot { is_library: true, file_set } |
45 | } | 45 | } |
46 | pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { | ||
47 | self.file_set.path_for_file(file) | ||
48 | } | ||
49 | pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> { | ||
50 | self.file_set.file_for_path(path) | ||
51 | } | ||
46 | pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { | 52 | pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { |
47 | self.file_set.iter() | 53 | self.file_set.iter() |
48 | } | 54 | } |
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs index 33bed6991..daea2aa95 100644 --- a/crates/ide/src/completion.rs +++ b/crates/ide/src/completion.rs | |||
@@ -19,6 +19,7 @@ mod complete_unqualified_path; | |||
19 | mod complete_postfix; | 19 | mod complete_postfix; |
20 | mod complete_macro_in_item_position; | 20 | mod complete_macro_in_item_position; |
21 | mod complete_trait_impl; | 21 | mod complete_trait_impl; |
22 | mod complete_mod; | ||
22 | 23 | ||
23 | use ide_db::RootDatabase; | 24 | use ide_db::RootDatabase; |
24 | 25 | ||
@@ -124,6 +125,7 @@ pub(crate) fn completions( | |||
124 | complete_postfix::complete_postfix(&mut acc, &ctx); | 125 | complete_postfix::complete_postfix(&mut acc, &ctx); |
125 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | 126 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
126 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); | 127 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); |
128 | complete_mod::complete_mod(&mut acc, &ctx); | ||
127 | 129 | ||
128 | Some(acc) | 130 | Some(acc) |
129 | } | 131 | } |
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs index 0abfaebcb..f4a9864d1 100644 --- a/crates/ide/src/completion/complete_attribute.rs +++ b/crates/ide/src/completion/complete_attribute.rs | |||
@@ -13,6 +13,10 @@ use crate::completion::{ | |||
13 | }; | 13 | }; |
14 | 14 | ||
15 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 15 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
16 | if ctx.mod_declaration_under_caret.is_some() { | ||
17 | return None; | ||
18 | } | ||
19 | |||
16 | let attribute = ctx.attribute_under_caret.as_ref()?; | 20 | let attribute = ctx.attribute_under_caret.as_ref()?; |
17 | match (attribute.path(), attribute.token_tree()) { | 21 | match (attribute.path(), attribute.token_tree()) { |
18 | (Some(path), Some(token_tree)) if path.to_string() == "derive" => { | 22 | (Some(path), Some(token_tree)) if path.to_string() == "derive" => { |
diff --git a/crates/ide/src/completion/complete_mod.rs b/crates/ide/src/completion/complete_mod.rs new file mode 100644 index 000000000..3cfc2e131 --- /dev/null +++ b/crates/ide/src/completion/complete_mod.rs | |||
@@ -0,0 +1,324 @@ | |||
1 | //! Completes mod declarations. | ||
2 | |||
3 | use base_db::{SourceDatabaseExt, VfsPath}; | ||
4 | use hir::{Module, ModuleSource}; | ||
5 | use ide_db::RootDatabase; | ||
6 | use rustc_hash::FxHashSet; | ||
7 | |||
8 | use crate::{CompletionItem, CompletionItemKind}; | ||
9 | |||
10 | use super::{ | ||
11 | completion_context::CompletionContext, completion_item::CompletionKind, | ||
12 | completion_item::Completions, | ||
13 | }; | ||
14 | |||
15 | /// Complete mod declaration, i.e. `mod <|> ;` | ||
16 | pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
17 | let mod_under_caret = match &ctx.mod_declaration_under_caret { | ||
18 | Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None, | ||
19 | Some(mod_under_caret) => mod_under_caret, | ||
20 | None => return None, | ||
21 | }; | ||
22 | |||
23 | let _p = profile::span("completion::complete_mod"); | ||
24 | |||
25 | let current_module = ctx.scope.module()?; | ||
26 | |||
27 | let module_definition_file = | ||
28 | current_module.definition_source(ctx.db).file_id.original_file(ctx.db); | ||
29 | let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file)); | ||
30 | let directory_to_look_for_submodules = directory_to_look_for_submodules( | ||
31 | current_module, | ||
32 | ctx.db, | ||
33 | source_root.path_for_file(&module_definition_file)?, | ||
34 | )?; | ||
35 | |||
36 | let existing_mod_declarations = current_module | ||
37 | .children(ctx.db) | ||
38 | .filter_map(|module| Some(module.name(ctx.db)?.to_string())) | ||
39 | .collect::<FxHashSet<_>>(); | ||
40 | |||
41 | let module_declaration_file = | ||
42 | current_module.declaration_source(ctx.db).map(|module_declaration_source_file| { | ||
43 | module_declaration_source_file.file_id.original_file(ctx.db) | ||
44 | }); | ||
45 | |||
46 | source_root | ||
47 | .iter() | ||
48 | .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file) | ||
49 | .filter(|submodule_candidate_file| { | ||
50 | Some(submodule_candidate_file) != module_declaration_file.as_ref() | ||
51 | }) | ||
52 | .filter_map(|submodule_file| { | ||
53 | let submodule_path = source_root.path_for_file(&submodule_file)?; | ||
54 | let directory_with_submodule = submodule_path.parent()?; | ||
55 | match submodule_path.name_and_extension()? { | ||
56 | ("lib", Some("rs")) | ("main", Some("rs")) => None, | ||
57 | ("mod", Some("rs")) => { | ||
58 | if directory_with_submodule.parent()? == directory_to_look_for_submodules { | ||
59 | match directory_with_submodule.name_and_extension()? { | ||
60 | (directory_name, None) => Some(directory_name.to_owned()), | ||
61 | _ => None, | ||
62 | } | ||
63 | } else { | ||
64 | None | ||
65 | } | ||
66 | } | ||
67 | (file_name, Some("rs")) | ||
68 | if directory_with_submodule == directory_to_look_for_submodules => | ||
69 | { | ||
70 | Some(file_name.to_owned()) | ||
71 | } | ||
72 | _ => None, | ||
73 | } | ||
74 | }) | ||
75 | .filter(|name| !existing_mod_declarations.contains(name)) | ||
76 | .for_each(|submodule_name| { | ||
77 | let mut label = submodule_name; | ||
78 | if mod_under_caret.semicolon_token().is_none() { | ||
79 | label.push(';') | ||
80 | } | ||
81 | acc.add( | ||
82 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label) | ||
83 | .kind(CompletionItemKind::Module), | ||
84 | ) | ||
85 | }); | ||
86 | |||
87 | Some(()) | ||
88 | } | ||
89 | |||
90 | fn directory_to_look_for_submodules( | ||
91 | module: Module, | ||
92 | db: &RootDatabase, | ||
93 | module_file_path: &VfsPath, | ||
94 | ) -> Option<VfsPath> { | ||
95 | let directory_with_module_path = module_file_path.parent()?; | ||
96 | let base_directory = match module_file_path.name_and_extension()? { | ||
97 | ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => { | ||
98 | Some(directory_with_module_path) | ||
99 | } | ||
100 | (regular_rust_file_name, Some("rs")) => { | ||
101 | if matches!( | ||
102 | ( | ||
103 | directory_with_module_path | ||
104 | .parent() | ||
105 | .as_ref() | ||
106 | .and_then(|path| path.name_and_extension()), | ||
107 | directory_with_module_path.name_and_extension(), | ||
108 | ), | ||
109 | (Some(("src", None)), Some(("bin", None))) | ||
110 | ) { | ||
111 | // files in /src/bin/ can import each other directly | ||
112 | Some(directory_with_module_path) | ||
113 | } else { | ||
114 | directory_with_module_path.join(regular_rust_file_name) | ||
115 | } | ||
116 | } | ||
117 | _ => None, | ||
118 | }?; | ||
119 | |||
120 | let mut resulting_path = base_directory; | ||
121 | for module in module_chain_to_containing_module_file(module, db) { | ||
122 | if let Some(name) = module.name(db) { | ||
123 | resulting_path = resulting_path.join(&name.to_string())?; | ||
124 | } | ||
125 | } | ||
126 | |||
127 | Some(resulting_path) | ||
128 | } | ||
129 | |||
130 | fn module_chain_to_containing_module_file( | ||
131 | current_module: Module, | ||
132 | db: &RootDatabase, | ||
133 | ) -> Vec<Module> { | ||
134 | let mut path = Vec::new(); | ||
135 | |||
136 | let mut current_module = Some(current_module); | ||
137 | while let Some(ModuleSource::Module(_)) = | ||
138 | current_module.map(|module| module.definition_source(db).value) | ||
139 | { | ||
140 | if let Some(module) = current_module { | ||
141 | path.insert(0, module); | ||
142 | current_module = module.parent(db); | ||
143 | } else { | ||
144 | current_module = None; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | path | ||
149 | } | ||
150 | |||
151 | #[cfg(test)] | ||
152 | mod tests { | ||
153 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
154 | use expect_test::{expect, Expect}; | ||
155 | |||
156 | fn check(ra_fixture: &str, expect: Expect) { | ||
157 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
158 | expect.assert_eq(&actual); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn lib_module_completion() { | ||
163 | check( | ||
164 | r#" | ||
165 | //- /lib.rs | ||
166 | mod <|> | ||
167 | //- /foo.rs | ||
168 | fn foo() {} | ||
169 | //- /foo/ignored_foo.rs | ||
170 | fn ignored_foo() {} | ||
171 | //- /bar/mod.rs | ||
172 | fn bar() {} | ||
173 | //- /bar/ignored_bar.rs | ||
174 | fn ignored_bar() {} | ||
175 | "#, | ||
176 | expect![[r#" | ||
177 | md bar; | ||
178 | md foo; | ||
179 | "#]], | ||
180 | ); | ||
181 | } | ||
182 | |||
183 | #[test] | ||
184 | fn no_module_completion_with_module_body() { | ||
185 | check( | ||
186 | r#" | ||
187 | //- /lib.rs | ||
188 | mod <|> { | ||
189 | |||
190 | } | ||
191 | //- /foo.rs | ||
192 | fn foo() {} | ||
193 | "#, | ||
194 | expect![[r#""#]], | ||
195 | ); | ||
196 | } | ||
197 | |||
198 | #[test] | ||
199 | fn main_module_completion() { | ||
200 | check( | ||
201 | r#" | ||
202 | //- /main.rs | ||
203 | mod <|> | ||
204 | //- /foo.rs | ||
205 | fn foo() {} | ||
206 | //- /foo/ignored_foo.rs | ||
207 | fn ignored_foo() {} | ||
208 | //- /bar/mod.rs | ||
209 | fn bar() {} | ||
210 | //- /bar/ignored_bar.rs | ||
211 | fn ignored_bar() {} | ||
212 | "#, | ||
213 | expect![[r#" | ||
214 | md bar; | ||
215 | md foo; | ||
216 | "#]], | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn main_test_module_completion() { | ||
222 | check( | ||
223 | r#" | ||
224 | //- /main.rs | ||
225 | mod tests { | ||
226 | mod <|>; | ||
227 | } | ||
228 | //- /tests/foo.rs | ||
229 | fn foo() {} | ||
230 | "#, | ||
231 | expect![[r#" | ||
232 | md foo | ||
233 | "#]], | ||
234 | ); | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn directly_nested_module_completion() { | ||
239 | check( | ||
240 | r#" | ||
241 | //- /lib.rs | ||
242 | mod foo; | ||
243 | //- /foo.rs | ||
244 | mod <|>; | ||
245 | //- /foo/bar.rs | ||
246 | fn bar() {} | ||
247 | //- /foo/bar/ignored_bar.rs | ||
248 | fn ignored_bar() {} | ||
249 | //- /foo/baz/mod.rs | ||
250 | fn baz() {} | ||
251 | //- /foo/moar/ignored_moar.rs | ||
252 | fn ignored_moar() {} | ||
253 | "#, | ||
254 | expect![[r#" | ||
255 | md bar | ||
256 | md baz | ||
257 | "#]], | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn nested_in_source_module_completion() { | ||
263 | check( | ||
264 | r#" | ||
265 | //- /lib.rs | ||
266 | mod foo; | ||
267 | //- /foo.rs | ||
268 | mod bar { | ||
269 | mod <|> | ||
270 | } | ||
271 | //- /foo/bar/baz.rs | ||
272 | fn baz() {} | ||
273 | "#, | ||
274 | expect![[r#" | ||
275 | md baz; | ||
276 | "#]], | ||
277 | ); | ||
278 | } | ||
279 | |||
280 | // FIXME binary modules are not supported in tests properly | ||
281 | // Binary modules are a bit special, they allow importing the modules from `/src/bin` | ||
282 | // and that's why are good to test two things: | ||
283 | // * no cycles are allowed in mod declarations | ||
284 | // * no modules from the parent directory are proposed | ||
285 | // Unfortunately, binary modules support is in cargo not rustc, | ||
286 | // hence the test does not work now | ||
287 | // | ||
288 | // #[test] | ||
289 | // fn regular_bin_module_completion() { | ||
290 | // check( | ||
291 | // r#" | ||
292 | // //- /src/bin.rs | ||
293 | // fn main() {} | ||
294 | // //- /src/bin/foo.rs | ||
295 | // mod <|> | ||
296 | // //- /src/bin/bar.rs | ||
297 | // fn bar() {} | ||
298 | // //- /src/bin/bar/bar_ignored.rs | ||
299 | // fn bar_ignored() {} | ||
300 | // "#, | ||
301 | // expect![[r#" | ||
302 | // md bar; | ||
303 | // "#]], | ||
304 | // ); | ||
305 | // } | ||
306 | |||
307 | #[test] | ||
308 | fn already_declared_bin_module_completion_omitted() { | ||
309 | check( | ||
310 | r#" | ||
311 | //- /src/bin.rs | ||
312 | fn main() {} | ||
313 | //- /src/bin/foo.rs | ||
314 | mod <|> | ||
315 | //- /src/bin/bar.rs | ||
316 | mod foo; | ||
317 | fn bar() {} | ||
318 | //- /src/bin/bar/bar_ignored.rs | ||
319 | fn bar_ignored() {} | ||
320 | "#, | ||
321 | expect![[r#""#]], | ||
322 | ); | ||
323 | } | ||
324 | } | ||
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs index accb09f7e..79de50792 100644 --- a/crates/ide/src/completion/complete_qualified_path.rs +++ b/crates/ide/src/completion/complete_qualified_path.rs | |||
@@ -13,7 +13,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
13 | None => return, | 13 | None => return, |
14 | }; | 14 | }; |
15 | 15 | ||
16 | if ctx.attribute_under_caret.is_some() { | 16 | if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() { |
17 | return; | 17 | return; |
18 | } | 18 | } |
19 | 19 | ||
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs index 1f1b682a7..8eda4b64d 100644 --- a/crates/ide/src/completion/complete_unqualified_path.rs +++ b/crates/ide/src/completion/complete_unqualified_path.rs | |||
@@ -13,6 +13,7 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
13 | if ctx.record_lit_syntax.is_some() | 13 | if ctx.record_lit_syntax.is_some() |
14 | || ctx.record_pat_syntax.is_some() | 14 | || ctx.record_pat_syntax.is_some() |
15 | || ctx.attribute_under_caret.is_some() | 15 | || ctx.attribute_under_caret.is_some() |
16 | || ctx.mod_declaration_under_caret.is_some() | ||
16 | { | 17 | { |
17 | return; | 18 | return; |
18 | } | 19 | } |
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs index 47355d5dc..161f59c1e 100644 --- a/crates/ide/src/completion/completion_context.rs +++ b/crates/ide/src/completion/completion_context.rs | |||
@@ -77,6 +77,7 @@ pub(crate) struct CompletionContext<'a> { | |||
77 | pub(super) is_path_type: bool, | 77 | pub(super) is_path_type: bool, |
78 | pub(super) has_type_args: bool, | 78 | pub(super) has_type_args: bool, |
79 | pub(super) attribute_under_caret: Option<ast::Attr>, | 79 | pub(super) attribute_under_caret: Option<ast::Attr>, |
80 | pub(super) mod_declaration_under_caret: Option<ast::Module>, | ||
80 | pub(super) unsafe_is_prev: bool, | 81 | pub(super) unsafe_is_prev: bool, |
81 | pub(super) if_is_prev: bool, | 82 | pub(super) if_is_prev: bool, |
82 | pub(super) block_expr_parent: bool, | 83 | pub(super) block_expr_parent: bool, |
@@ -152,6 +153,7 @@ impl<'a> CompletionContext<'a> { | |||
152 | has_type_args: false, | 153 | has_type_args: false, |
153 | dot_receiver_is_ambiguous_float_literal: false, | 154 | dot_receiver_is_ambiguous_float_literal: false, |
154 | attribute_under_caret: None, | 155 | attribute_under_caret: None, |
156 | mod_declaration_under_caret: None, | ||
155 | unsafe_is_prev: false, | 157 | unsafe_is_prev: false, |
156 | in_loop_body: false, | 158 | in_loop_body: false, |
157 | ref_pat_parent: false, | 159 | ref_pat_parent: false, |
@@ -238,7 +240,10 @@ impl<'a> CompletionContext<'a> { | |||
238 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | 240 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); |
239 | self.is_match_arm = is_match_arm(syntax_element.clone()); | 241 | self.is_match_arm = is_match_arm(syntax_element.clone()); |
240 | self.has_item_list_or_source_file_parent = | 242 | self.has_item_list_or_source_file_parent = |
241 | has_item_list_or_source_file_parent(syntax_element); | 243 | has_item_list_or_source_file_parent(syntax_element.clone()); |
244 | self.mod_declaration_under_caret = | ||
245 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | ||
246 | .filter(|module| module.item_list().is_none()); | ||
242 | } | 247 | } |
243 | 248 | ||
244 | fn fill( | 249 | fn fill( |
diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs index c6ae589db..b17ddf133 100644 --- a/crates/ide/src/completion/patterns.rs +++ b/crates/ide/src/completion/patterns.rs | |||
@@ -115,6 +115,7 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | |||
115 | .filter(|it| it.kind() == IF_KW) | 115 | .filter(|it| it.kind() == IF_KW) |
116 | .is_some() | 116 | .is_some() |
117 | } | 117 | } |
118 | |||
118 | #[test] | 119 | #[test] |
119 | fn test_if_is_prev() { | 120 | fn test_if_is_prev() { |
120 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | 121 | check_pattern_is_applicable(r"if l<|>", if_is_prev); |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 25d6f7abd..d9fc25d88 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -4,7 +4,7 @@ mod injection; | |||
4 | #[cfg(test)] | 4 | #[cfg(test)] |
5 | mod tests; | 5 | mod tests; |
6 | 6 | ||
7 | use hir::{Name, Semantics, VariantDef}; | 7 | use hir::{Local, Name, Semantics, VariantDef}; |
8 | use ide_db::{ | 8 | use ide_db::{ |
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, |
10 | RootDatabase, | 10 | RootDatabase, |
@@ -13,8 +13,8 @@ use rustc_hash::FxHashMap; | |||
13 | use syntax::{ | 13 | use syntax::{ |
14 | ast::{self, HasFormatSpecifier}, | 14 | ast::{self, HasFormatSpecifier}, |
15 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | 15 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, |
16 | SyntaxKind::*, | 16 | SyntaxKind::{self, *}, |
17 | TextRange, WalkEvent, T, | 17 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
18 | }; | 18 | }; |
19 | 19 | ||
20 | use crate::FileId; | 20 | use crate::FileId; |
@@ -454,6 +454,32 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
454 | Some(TextRange::new(range_start, range_end)) | 454 | Some(TextRange::new(range_start, range_end)) |
455 | } | 455 | } |
456 | 456 | ||
457 | /// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly. | ||
458 | fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool { | ||
459 | while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) { | ||
460 | if parent.kind() != *kind { | ||
461 | return false; | ||
462 | } | ||
463 | |||
464 | // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value | ||
465 | // in the same pattern is unstable: rust-lang/rust#68354. | ||
466 | node = node.parent().unwrap().into(); | ||
467 | kinds = rest; | ||
468 | } | ||
469 | |||
470 | // Only true if we matched all expected kinds | ||
471 | kinds.len() == 0 | ||
472 | } | ||
473 | |||
474 | fn is_consumed_lvalue( | ||
475 | node: NodeOrToken<SyntaxNode, SyntaxToken>, | ||
476 | local: &Local, | ||
477 | db: &RootDatabase, | ||
478 | ) -> bool { | ||
479 | // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming. | ||
480 | parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db) | ||
481 | } | ||
482 | |||
457 | fn highlight_element( | 483 | fn highlight_element( |
458 | sema: &Semantics<RootDatabase>, | 484 | sema: &Semantics<RootDatabase>, |
459 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 485 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
@@ -522,6 +548,12 @@ fn highlight_element( | |||
522 | 548 | ||
523 | let mut h = highlight_def(db, def); | 549 | let mut h = highlight_def(db, def); |
524 | 550 | ||
551 | if let Definition::Local(local) = &def { | ||
552 | if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) { | ||
553 | h |= HighlightModifier::Consuming; | ||
554 | } | ||
555 | } | ||
556 | |||
525 | if let Some(parent) = name_ref.syntax().parent() { | 557 | if let Some(parent) = name_ref.syntax().parent() { |
526 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { | 558 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { |
527 | if let Definition::Field(field) = def { | 559 | if let Definition::Field(field) = def { |
@@ -645,21 +677,30 @@ fn highlight_element( | |||
645 | .and_then(ast::SelfParam::cast) | 677 | .and_then(ast::SelfParam::cast) |
646 | .and_then(|p| p.mut_token()) | 678 | .and_then(|p| p.mut_token()) |
647 | .is_some(); | 679 | .is_some(); |
648 | // closure to enforce lazyness | 680 | let self_path = &element |
649 | let self_path = || { | 681 | .parent() |
650 | sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?) | 682 | .as_ref() |
651 | }; | 683 | .and_then(SyntaxNode::parent) |
684 | .and_then(ast::Path::cast) | ||
685 | .and_then(|p| sema.resolve_path(&p)); | ||
686 | let mut h = HighlightTag::SelfKeyword.into(); | ||
652 | if self_param_is_mut | 687 | if self_param_is_mut |
653 | || matches!(self_path(), | 688 | || matches!(self_path, |
654 | Some(hir::PathResolution::Local(local)) | 689 | Some(hir::PathResolution::Local(local)) |
655 | if local.is_self(db) | 690 | if local.is_self(db) |
656 | && (local.is_mut(db) || local.ty(db).is_mutable_reference()) | 691 | && (local.is_mut(db) || local.ty(db).is_mutable_reference()) |
657 | ) | 692 | ) |
658 | { | 693 | { |
659 | HighlightTag::SelfKeyword | HighlightModifier::Mutable | 694 | h |= HighlightModifier::Mutable |
660 | } else { | ||
661 | HighlightTag::SelfKeyword.into() | ||
662 | } | 695 | } |
696 | |||
697 | if let Some(hir::PathResolution::Local(local)) = self_path { | ||
698 | if is_consumed_lvalue(element, &local, db) { | ||
699 | h |= HighlightModifier::Consuming; | ||
700 | } | ||
701 | } | ||
702 | |||
703 | h | ||
663 | } | 704 | } |
664 | T![ref] => element | 705 | T![ref] => element |
665 | .parent() | 706 | .parent() |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index d0df2e0ec..cde42024c 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html | |||
@@ -61,8 +61,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
61 | <span class="punctuation">}</span> | 61 | <span class="punctuation">}</span> |
62 | 62 | ||
63 | <span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> | 63 | <span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> |
64 | <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> | 64 | <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> |
65 | <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> | 65 | <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword consuming">self</span><span class="punctuation">)</span> |
66 | <span class="punctuation">}</span> | 66 | <span class="punctuation">}</span> |
67 | 67 | ||
68 | <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> | 68 | <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> |
@@ -80,8 +80,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
80 | <span class="punctuation">}</span> | 80 | <span class="punctuation">}</span> |
81 | 81 | ||
82 | <span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> | 82 | <span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> |
83 | <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="punctuation">{</span> | 83 | <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">FooCopy</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="punctuation">{</span> |
84 | <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> | 84 | <span class="value_param">f</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span> |
85 | <span class="punctuation">}</span> | 85 | <span class="punctuation">}</span> |
86 | 86 | ||
87 | <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> | 87 | <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> |
@@ -144,14 +144,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
144 | <span class="variable">y</span><span class="punctuation">;</span> | 144 | <span class="variable">y</span><span class="punctuation">;</span> |
145 | 145 | ||
146 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> | 146 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> |
147 | <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="unresolved_reference">clone</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | ||
147 | <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 148 | <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
148 | <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 149 | <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
149 | <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 150 | <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="variable consuming">foo2</span><span class="punctuation">)</span><span class="punctuation">;</span> |
150 | 151 | ||
151 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> | 152 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> |
152 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 153 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
153 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 154 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
154 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 155 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span> |
155 | <span class="punctuation">}</span> | 156 | <span class="punctuation">}</span> |
156 | 157 | ||
157 | <span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation"><</span><span class="type_param declaration">T</span><span class="punctuation">></span> <span class="punctuation">{</span> | 158 | <span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation"><</span><span class="type_param declaration">T</span><span class="punctuation">></span> <span class="punctuation">{</span> |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 6f72a29bd..57d4e1252 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -35,8 +35,8 @@ impl Bar for Foo { | |||
35 | } | 35 | } |
36 | 36 | ||
37 | impl Foo { | 37 | impl Foo { |
38 | fn baz(mut self) -> i32 { | 38 | fn baz(mut self, f: Foo) -> i32 { |
39 | self.x | 39 | f.baz(self) |
40 | } | 40 | } |
41 | 41 | ||
42 | fn qux(&mut self) { | 42 | fn qux(&mut self) { |
@@ -54,8 +54,8 @@ struct FooCopy { | |||
54 | } | 54 | } |
55 | 55 | ||
56 | impl FooCopy { | 56 | impl FooCopy { |
57 | fn baz(self) -> u32 { | 57 | fn baz(self, f: FooCopy) -> u32 { |
58 | self.x | 58 | f.baz(self) |
59 | } | 59 | } |
60 | 60 | ||
61 | fn qux(&mut self) { | 61 | fn qux(&mut self) { |
@@ -118,14 +118,15 @@ fn main() { | |||
118 | y; | 118 | y; |
119 | 119 | ||
120 | let mut foo = Foo { x, y: x }; | 120 | let mut foo = Foo { x, y: x }; |
121 | let foo2 = foo.clone(); | ||
121 | foo.quop(); | 122 | foo.quop(); |
122 | foo.qux(); | 123 | foo.qux(); |
123 | foo.baz(); | 124 | foo.baz(foo2); |
124 | 125 | ||
125 | let mut copy = FooCopy { x }; | 126 | let mut copy = FooCopy { x }; |
126 | copy.quop(); | 127 | copy.quop(); |
127 | copy.qux(); | 128 | copy.qux(); |
128 | copy.baz(); | 129 | copy.baz(copy); |
129 | } | 130 | } |
130 | 131 | ||
131 | enum Option<T> { | 132 | enum Option<T> { |
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 2d91939ce..288c39e49 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs | |||
@@ -33,7 +33,7 @@ pub enum ProjectWorkspace { | |||
33 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. | 33 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. |
34 | Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, | 34 | Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, |
35 | /// Project workspace was manually specified using a `rust-project.json` file. | 35 | /// Project workspace was manually specified using a `rust-project.json` file. |
36 | Json { project: ProjectJson }, | 36 | Json { project: ProjectJson, sysroot: Option<Sysroot> }, |
37 | } | 37 | } |
38 | 38 | ||
39 | impl fmt::Debug for ProjectWorkspace { | 39 | impl fmt::Debug for ProjectWorkspace { |
@@ -44,10 +44,10 @@ impl fmt::Debug for ProjectWorkspace { | |||
44 | .field("n_packages", &cargo.packages().len()) | 44 | .field("n_packages", &cargo.packages().len()) |
45 | .field("n_sysroot_crates", &sysroot.crates().len()) | 45 | .field("n_sysroot_crates", &sysroot.crates().len()) |
46 | .finish(), | 46 | .finish(), |
47 | ProjectWorkspace::Json { project } => { | 47 | ProjectWorkspace::Json { project, sysroot } => { |
48 | let mut debug_struct = f.debug_struct("Json"); | 48 | let mut debug_struct = f.debug_struct("Json"); |
49 | debug_struct.field("n_crates", &project.n_crates()); | 49 | debug_struct.field("n_crates", &project.n_crates()); |
50 | if let Some(sysroot) = &project.sysroot { | 50 | if let Some(sysroot) = sysroot { |
51 | debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); | 51 | debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); |
52 | } | 52 | } |
53 | debug_struct.finish() | 53 | debug_struct.finish() |
@@ -169,7 +169,11 @@ impl ProjectWorkspace { | |||
169 | })?; | 169 | })?; |
170 | let project_location = project_json.parent().unwrap().to_path_buf(); | 170 | let project_location = project_json.parent().unwrap().to_path_buf(); |
171 | let project = ProjectJson::new(&project_location, data); | 171 | let project = ProjectJson::new(&project_location, data); |
172 | ProjectWorkspace::Json { project } | 172 | let sysroot = match &project.sysroot_src { |
173 | Some(path) => Some(Sysroot::load(path)?), | ||
174 | None => None, | ||
175 | }; | ||
176 | ProjectWorkspace::Json { project, sysroot } | ||
173 | } | 177 | } |
174 | ProjectManifest::CargoToml(cargo_toml) => { | 178 | ProjectManifest::CargoToml(cargo_toml) => { |
175 | let cargo_version = utf8_stdout({ | 179 | let cargo_version = utf8_stdout({ |
@@ -203,12 +207,21 @@ impl ProjectWorkspace { | |||
203 | Ok(res) | 207 | Ok(res) |
204 | } | 208 | } |
205 | 209 | ||
210 | pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> { | ||
211 | let sysroot = match &project_json.sysroot_src { | ||
212 | Some(path) => Some(Sysroot::load(path)?), | ||
213 | None => None, | ||
214 | }; | ||
215 | |||
216 | Ok(ProjectWorkspace::Json { project: project_json, sysroot }) | ||
217 | } | ||
218 | |||
206 | /// Returns the roots for the current `ProjectWorkspace` | 219 | /// Returns the roots for the current `ProjectWorkspace` |
207 | /// The return type contains the path and whether or not | 220 | /// The return type contains the path and whether or not |
208 | /// the root is a member of the current workspace | 221 | /// the root is a member of the current workspace |
209 | pub fn to_roots(&self) -> Vec<PackageRoot> { | 222 | pub fn to_roots(&self) -> Vec<PackageRoot> { |
210 | match self { | 223 | match self { |
211 | ProjectWorkspace::Json { project } => project | 224 | ProjectWorkspace::Json { project, sysroot } => project |
212 | .crates() | 225 | .crates() |
213 | .map(|(_, krate)| PackageRoot { | 226 | .map(|(_, krate)| PackageRoot { |
214 | is_member: krate.is_workspace_member, | 227 | is_member: krate.is_workspace_member, |
@@ -217,7 +230,7 @@ impl ProjectWorkspace { | |||
217 | }) | 230 | }) |
218 | .collect::<FxHashSet<_>>() | 231 | .collect::<FxHashSet<_>>() |
219 | .into_iter() | 232 | .into_iter() |
220 | .chain(project.sysroot.as_ref().into_iter().flat_map(|sysroot| { | 233 | .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| { |
221 | sysroot.crates().map(move |krate| PackageRoot { | 234 | sysroot.crates().map(move |krate| PackageRoot { |
222 | is_member: false, | 235 | is_member: false, |
223 | include: vec![sysroot[krate].root_dir().to_path_buf()], | 236 | include: vec![sysroot[krate].root_dir().to_path_buf()], |
@@ -255,7 +268,7 @@ impl ProjectWorkspace { | |||
255 | 268 | ||
256 | pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> { | 269 | pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> { |
257 | match self { | 270 | match self { |
258 | ProjectWorkspace::Json { project } => project | 271 | ProjectWorkspace::Json { project, sysroot: _ } => project |
259 | .crates() | 272 | .crates() |
260 | .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) | 273 | .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) |
261 | .cloned() | 274 | .cloned() |
@@ -285,9 +298,8 @@ impl ProjectWorkspace { | |||
285 | ) -> CrateGraph { | 298 | ) -> CrateGraph { |
286 | let mut crate_graph = CrateGraph::default(); | 299 | let mut crate_graph = CrateGraph::default(); |
287 | match self { | 300 | match self { |
288 | ProjectWorkspace::Json { project } => { | 301 | ProjectWorkspace::Json { project, sysroot } => { |
289 | let sysroot_dps = project | 302 | let sysroot_dps = sysroot |
290 | .sysroot | ||
291 | .as_ref() | 303 | .as_ref() |
292 | .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); | 304 | .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); |
293 | 305 | ||
diff --git a/crates/project_model/src/project_json.rs b/crates/project_model/src/project_json.rs index 5a0fe749a..979e90058 100644 --- a/crates/project_model/src/project_json.rs +++ b/crates/project_model/src/project_json.rs | |||
@@ -7,12 +7,12 @@ use paths::{AbsPath, AbsPathBuf}; | |||
7 | use rustc_hash::FxHashMap; | 7 | use rustc_hash::FxHashMap; |
8 | use serde::{de, Deserialize}; | 8 | use serde::{de, Deserialize}; |
9 | 9 | ||
10 | use crate::{cfg_flag::CfgFlag, Sysroot}; | 10 | use crate::cfg_flag::CfgFlag; |
11 | 11 | ||
12 | /// Roots and crates that compose this Rust project. | 12 | /// Roots and crates that compose this Rust project. |
13 | #[derive(Clone, Debug, Eq, PartialEq)] | 13 | #[derive(Clone, Debug, Eq, PartialEq)] |
14 | pub struct ProjectJson { | 14 | pub struct ProjectJson { |
15 | pub(crate) sysroot: Option<Sysroot>, | 15 | pub(crate) sysroot_src: Option<AbsPathBuf>, |
16 | crates: Vec<Crate>, | 16 | crates: Vec<Crate>, |
17 | } | 17 | } |
18 | 18 | ||
@@ -35,7 +35,7 @@ pub struct Crate { | |||
35 | impl ProjectJson { | 35 | impl ProjectJson { |
36 | pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { | 36 | pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { |
37 | ProjectJson { | 37 | ProjectJson { |
38 | sysroot: data.sysroot_src.map(|it| base.join(it)).map(|it| Sysroot::load(&it)), | 38 | sysroot_src: data.sysroot_src.map(|it| base.join(it)), |
39 | crates: data | 39 | crates: data |
40 | .crates | 40 | .crates |
41 | .into_iter() | 41 | .into_iter() |
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs index 74c0eda9a..871808d89 100644 --- a/crates/project_model/src/sysroot.rs +++ b/crates/project_model/src/sysroot.rs | |||
@@ -51,11 +51,11 @@ impl Sysroot { | |||
51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { | 51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { |
52 | let current_dir = cargo_toml.parent().unwrap(); | 52 | let current_dir = cargo_toml.parent().unwrap(); |
53 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; | 53 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; |
54 | let res = Sysroot::load(&sysroot_src_dir); | 54 | let res = Sysroot::load(&sysroot_src_dir)?; |
55 | Ok(res) | 55 | Ok(res) |
56 | } | 56 | } |
57 | 57 | ||
58 | pub fn load(sysroot_src_dir: &AbsPath) -> Sysroot { | 58 | pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> { |
59 | let mut sysroot = Sysroot { crates: Arena::default() }; | 59 | let mut sysroot = Sysroot { crates: Arena::default() }; |
60 | 60 | ||
61 | for name in SYSROOT_CRATES.trim().lines() { | 61 | for name in SYSROOT_CRATES.trim().lines() { |
@@ -89,7 +89,14 @@ impl Sysroot { | |||
89 | } | 89 | } |
90 | } | 90 | } |
91 | 91 | ||
92 | sysroot | 92 | if sysroot.by_name("core").is_none() { |
93 | anyhow::bail!( | ||
94 | "could not find libcore in sysroot path `{}`", | ||
95 | sysroot_src_dir.as_ref().display() | ||
96 | ); | ||
97 | } | ||
98 | |||
99 | Ok(sysroot) | ||
93 | } | 100 | } |
94 | 101 | ||
95 | fn by_name(&self, name: &str) -> Option<SysrootCrate> { | 102 | fn by_name(&self, name: &str) -> Option<SysrootCrate> { |
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt index 89dae7d5a..00e8da8a7 100644 --- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt +++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt | |||
@@ -44,4 +44,49 @@ | |||
44 | }, | 44 | }, |
45 | fixes: [], | 45 | fixes: [], |
46 | }, | 46 | }, |
47 | MappedRustDiagnostic { | ||
48 | url: "file:///test/crates/hir_def/src/path.rs", | ||
49 | diagnostic: Diagnostic { | ||
50 | range: Range { | ||
51 | start: Position { | ||
52 | line: 264, | ||
53 | character: 8, | ||
54 | }, | ||
55 | end: Position { | ||
56 | line: 264, | ||
57 | character: 76, | ||
58 | }, | ||
59 | }, | ||
60 | severity: Some( | ||
61 | Error, | ||
62 | ), | ||
63 | code: None, | ||
64 | source: Some( | ||
65 | "rustc", | ||
66 | ), | ||
67 | message: "Please register your known path in the path module", | ||
68 | related_information: Some( | ||
69 | [ | ||
70 | DiagnosticRelatedInformation { | ||
71 | location: Location { | ||
72 | uri: "file:///test/crates/hir_def/src/data.rs", | ||
73 | range: Range { | ||
74 | start: Position { | ||
75 | line: 79, | ||
76 | character: 15, | ||
77 | }, | ||
78 | end: Position { | ||
79 | line: 79, | ||
80 | character: 41, | ||
81 | }, | ||
82 | }, | ||
83 | }, | ||
84 | message: "Exact error occured here", | ||
85 | }, | ||
86 | ], | ||
87 | ), | ||
88 | tags: None, | ||
89 | }, | ||
90 | fixes: [], | ||
91 | }, | ||
47 | ] | 92 | ] |
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index f69a949f2..33606edda 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -225,12 +225,43 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
225 | 225 | ||
226 | // If error occurs from macro expansion, add related info pointing to | 226 | // If error occurs from macro expansion, add related info pointing to |
227 | // where the error originated | 227 | // where the error originated |
228 | if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { | 228 | // Also, we would generate an additional diagnostic, so that exact place of macro |
229 | related_information.push(lsp_types::DiagnosticRelatedInformation { | 229 | // will be highlighted in the error origin place. |
230 | location: location_naive(workspace_root, &primary_span), | 230 | let additional_diagnostic = |
231 | message: "Error originated from macro here".to_string(), | 231 | if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { |
232 | }); | 232 | let in_macro_location = location_naive(workspace_root, &primary_span); |
233 | } | 233 | |
234 | // Add related information for the main disagnostic. | ||
235 | related_information.push(lsp_types::DiagnosticRelatedInformation { | ||
236 | location: in_macro_location.clone(), | ||
237 | message: "Error originated from macro here".to_string(), | ||
238 | }); | ||
239 | |||
240 | // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. | ||
241 | let information_for_additional_diagnostic = | ||
242 | vec![lsp_types::DiagnosticRelatedInformation { | ||
243 | location: location.clone(), | ||
244 | message: "Exact error occured here".to_string(), | ||
245 | }]; | ||
246 | |||
247 | let diagnostic = lsp_types::Diagnostic { | ||
248 | range: in_macro_location.range, | ||
249 | severity, | ||
250 | code: code.clone().map(lsp_types::NumberOrString::String), | ||
251 | source: Some(source.clone()), | ||
252 | message: message.clone(), | ||
253 | related_information: Some(information_for_additional_diagnostic), | ||
254 | tags: if tags.is_empty() { None } else { Some(tags.clone()) }, | ||
255 | }; | ||
256 | |||
257 | Some(MappedRustDiagnostic { | ||
258 | url: in_macro_location.uri, | ||
259 | diagnostic, | ||
260 | fixes: fixes.clone(), | ||
261 | }) | ||
262 | } else { | ||
263 | None | ||
264 | }; | ||
234 | 265 | ||
235 | let diagnostic = lsp_types::Diagnostic { | 266 | let diagnostic = lsp_types::Diagnostic { |
236 | range: location.range, | 267 | range: location.range, |
@@ -246,8 +277,14 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
246 | tags: if tags.is_empty() { None } else { Some(tags.clone()) }, | 277 | tags: if tags.is_empty() { None } else { Some(tags.clone()) }, |
247 | }; | 278 | }; |
248 | 279 | ||
249 | MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() } | 280 | let main_diagnostic = |
281 | MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() }; | ||
282 | match additional_diagnostic { | ||
283 | None => vec![main_diagnostic], | ||
284 | Some(additional_diagnostic) => vec![main_diagnostic, additional_diagnostic], | ||
285 | } | ||
250 | }) | 286 | }) |
287 | .flatten() | ||
251 | .collect() | 288 | .collect() |
252 | } | 289 | } |
253 | 290 | ||
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 20019b944..bab6f8a71 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs | |||
@@ -109,7 +109,7 @@ impl GlobalState { | |||
109 | ) | 109 | ) |
110 | } | 110 | } |
111 | LinkedProject::InlineJsonProject(it) => { | 111 | LinkedProject::InlineJsonProject(it) => { |
112 | Ok(project_model::ProjectWorkspace::Json { project: it.clone() }) | 112 | project_model::ProjectWorkspace::load_inline(it.clone()) |
113 | } | 113 | } |
114 | }) | 114 | }) |
115 | .collect::<Vec<_>>(); | 115 | .collect::<Vec<_>>(); |
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index e9196fcd2..4aa2d6526 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs | |||
@@ -23,13 +23,22 @@ impl FileSet { | |||
23 | let mut base = self.paths[&anchor].clone(); | 23 | let mut base = self.paths[&anchor].clone(); |
24 | base.pop(); | 24 | base.pop(); |
25 | let path = base.join(path)?; | 25 | let path = base.join(path)?; |
26 | let res = self.files.get(&path).copied(); | 26 | self.files.get(&path).copied() |
27 | res | 27 | } |
28 | |||
29 | pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> { | ||
30 | self.files.get(path) | ||
28 | } | 31 | } |
32 | |||
33 | pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { | ||
34 | self.paths.get(file) | ||
35 | } | ||
36 | |||
29 | pub fn insert(&mut self, file_id: FileId, path: VfsPath) { | 37 | pub fn insert(&mut self, file_id: FileId, path: VfsPath) { |
30 | self.files.insert(path.clone(), file_id); | 38 | self.files.insert(path.clone(), file_id); |
31 | self.paths.insert(file_id, path); | 39 | self.paths.insert(file_id, path); |
32 | } | 40 | } |
41 | |||
33 | pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { | 42 | pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { |
34 | self.paths.keys().copied() | 43 | self.paths.keys().copied() |
35 | } | 44 | } |
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index 944a702df..022a0be1e 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs | |||
@@ -48,6 +48,24 @@ impl VfsPath { | |||
48 | (VfsPathRepr::VirtualPath(_), _) => false, | 48 | (VfsPathRepr::VirtualPath(_), _) => false, |
49 | } | 49 | } |
50 | } | 50 | } |
51 | pub fn parent(&self) -> Option<VfsPath> { | ||
52 | let mut parent = self.clone(); | ||
53 | if parent.pop() { | ||
54 | Some(parent) | ||
55 | } else { | ||
56 | None | ||
57 | } | ||
58 | } | ||
59 | |||
60 | pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | ||
61 | match &self.0 { | ||
62 | VfsPathRepr::PathBuf(p) => Some(( | ||
63 | p.file_stem()?.to_str()?, | ||
64 | p.extension().and_then(|extension| extension.to_str()), | ||
65 | )), | ||
66 | VfsPathRepr::VirtualPath(p) => p.name_and_extension(), | ||
67 | } | ||
68 | } | ||
51 | 69 | ||
52 | // Don't make this `pub` | 70 | // Don't make this `pub` |
53 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { | 71 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { |
@@ -268,4 +286,60 @@ impl VirtualPath { | |||
268 | res.0 = format!("{}/{}", res.0, path); | 286 | res.0 = format!("{}/{}", res.0, path); |
269 | Some(res) | 287 | Some(res) |
270 | } | 288 | } |
289 | |||
290 | pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | ||
291 | let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 }; | ||
292 | let file_name = match file_path.rfind('/') { | ||
293 | Some(position) => &file_path[position + 1..], | ||
294 | None => file_path, | ||
295 | }; | ||
296 | |||
297 | if file_name.is_empty() { | ||
298 | None | ||
299 | } else { | ||
300 | let mut file_stem_and_extension = file_name.rsplitn(2, '.'); | ||
301 | let extension = file_stem_and_extension.next(); | ||
302 | let file_stem = file_stem_and_extension.next(); | ||
303 | |||
304 | match (file_stem, extension) { | ||
305 | (None, None) => None, | ||
306 | (None, Some(_)) | (Some(""), Some(_)) => Some((file_name, None)), | ||
307 | (Some(file_stem), extension) => Some((file_stem, extension)), | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | } | ||
312 | |||
313 | #[cfg(test)] | ||
314 | mod tests { | ||
315 | use super::*; | ||
316 | |||
317 | #[test] | ||
318 | fn virtual_path_extensions() { | ||
319 | assert_eq!(VirtualPath("/".to_string()).name_and_extension(), None); | ||
320 | assert_eq!( | ||
321 | VirtualPath("/directory".to_string()).name_and_extension(), | ||
322 | Some(("directory", None)) | ||
323 | ); | ||
324 | assert_eq!( | ||
325 | VirtualPath("/directory/".to_string()).name_and_extension(), | ||
326 | Some(("directory", None)) | ||
327 | ); | ||
328 | assert_eq!( | ||
329 | VirtualPath("/directory/file".to_string()).name_and_extension(), | ||
330 | Some(("file", None)) | ||
331 | ); | ||
332 | assert_eq!( | ||
333 | VirtualPath("/directory/.file".to_string()).name_and_extension(), | ||
334 | Some((".file", None)) | ||
335 | ); | ||
336 | assert_eq!( | ||
337 | VirtualPath("/directory/.file.rs".to_string()).name_and_extension(), | ||
338 | Some((".file", Some("rs"))) | ||
339 | ); | ||
340 | assert_eq!( | ||
341 | VirtualPath("/directory/file.rs".to_string()).name_and_extension(), | ||
342 | Some(("file", Some("rs"))) | ||
343 | ); | ||
344 | } | ||
271 | } | 345 | } |