diff options
-rw-r--r-- | crates/completion/src/completions/dot.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 57 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 7 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 240 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 37 | ||||
-rw-r--r-- | docs/dev/style.md | 2 | ||||
-rw-r--r-- | xtask/tests/tidy.rs | 36 |
7 files changed, 253 insertions, 134 deletions
diff --git a/crates/completion/src/completions/dot.rs b/crates/completion/src/completions/dot.rs index 2e25c8ba2..d04eef65a 100644 --- a/crates/completion/src/completions/dot.rs +++ b/crates/completion/src/completions/dot.rs | |||
@@ -373,20 +373,20 @@ fn foo(a: A) { | |||
373 | fn macro_expansion_resilient() { | 373 | fn macro_expansion_resilient() { |
374 | check( | 374 | check( |
375 | r#" | 375 | r#" |
376 | macro_rules! dbg { | 376 | macro_rules! d { |
377 | () => {}; | 377 | () => {}; |
378 | ($val:expr) => { | 378 | ($val:expr) => { |
379 | match $val { tmp => { tmp } } | 379 | match $val { tmp => { tmp } } |
380 | }; | 380 | }; |
381 | // Trailing comma with single argument is ignored | 381 | // Trailing comma with single argument is ignored |
382 | ($val:expr,) => { $crate::dbg!($val) }; | 382 | ($val:expr,) => { $crate::d!($val) }; |
383 | ($($val:expr),+ $(,)?) => { | 383 | ($($val:expr),+ $(,)?) => { |
384 | ($($crate::dbg!($val)),+,) | 384 | ($($crate::d!($val)),+,) |
385 | }; | 385 | }; |
386 | } | 386 | } |
387 | struct A { the_field: u32 } | 387 | struct A { the_field: u32 } |
388 | fn foo(a: A) { | 388 | fn foo(a: A) { |
389 | dbg!(a.$0) | 389 | d!(a.$0) |
390 | } | 390 | } |
391 | "#, | 391 | "#, |
392 | expect![[r#" | 392 | expect![[r#" |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index c20185b16..cd4afc804 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -6,15 +6,13 @@ use ide_db::{ | |||
6 | symbol_index, RootDatabase, | 6 | symbol_index, RootDatabase, |
7 | }; | 7 | }; |
8 | use syntax::{ | 8 | use syntax::{ |
9 | ast::{self, NameOwner}, | 9 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T, |
10 | match_ast, AstNode, AstToken, | ||
11 | SyntaxKind::*, | ||
12 | SyntaxToken, TextSize, TokenAtOffset, T, | ||
13 | }; | 10 | }; |
14 | 11 | ||
15 | use crate::{ | 12 | use crate::{ |
16 | display::{ToNav, TryToNav}, | 13 | display::{ToNav, TryToNav}, |
17 | doc_links::extract_definitions_from_markdown, | 14 | doc_links::extract_definitions_from_markdown, |
15 | runnables::doc_owner_to_def, | ||
18 | FilePosition, NavigationTarget, RangeInfo, SymbolKind, | 16 | FilePosition, NavigationTarget, RangeInfo, SymbolKind, |
19 | }; | 17 | }; |
20 | 18 | ||
@@ -84,31 +82,23 @@ fn def_for_doc_comment( | |||
84 | doc_comment: &ast::Comment, | 82 | doc_comment: &ast::Comment, |
85 | ) -> Option<hir::ModuleDef> { | 83 | ) -> Option<hir::ModuleDef> { |
86 | let parent = doc_comment.syntax().parent(); | 84 | let parent = doc_comment.syntax().parent(); |
87 | let db = sema.db; | ||
88 | let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; | 85 | let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; |
89 | let link = &link; | 86 | |
90 | let name = match_ast! { | 87 | let def = doc_owner_to_def(sema, parent)?; |
91 | match parent { | 88 | match def { |
92 | ast::Name(name) => Some(name), | ||
93 | ast::Fn(func) => func.name(), | ||
94 | _ => None, | ||
95 | } | ||
96 | }?; | ||
97 | let definition = NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db))?; | ||
98 | match definition { | ||
99 | Definition::ModuleDef(def) => match def { | 89 | Definition::ModuleDef(def) => match def { |
100 | ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), | 90 | ModuleDef::Module(it) => it.resolve_doc_path(sema.db, &link, ns), |
101 | ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns), | 91 | ModuleDef::Function(it) => it.resolve_doc_path(sema.db, &link, ns), |
102 | ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns), | 92 | ModuleDef::Adt(it) => it.resolve_doc_path(sema.db, &link, ns), |
103 | ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns), | 93 | ModuleDef::Variant(it) => it.resolve_doc_path(sema.db, &link, ns), |
104 | ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns), | 94 | ModuleDef::Const(it) => it.resolve_doc_path(sema.db, &link, ns), |
105 | ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns), | 95 | ModuleDef::Static(it) => it.resolve_doc_path(sema.db, &link, ns), |
106 | ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns), | 96 | ModuleDef::Trait(it) => it.resolve_doc_path(sema.db, &link, ns), |
107 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns), | 97 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(sema.db, &link, ns), |
108 | ModuleDef::BuiltinType(_) => return None, | 98 | ModuleDef::BuiltinType(_) => return None, |
109 | }, | 99 | }, |
110 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), | 100 | Definition::Macro(it) => it.resolve_doc_path(sema.db, &link, ns), |
111 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), | 101 | Definition::Field(it) => it.resolve_doc_path(sema.db, &link, ns), |
112 | Definition::SelfType(_) | 102 | Definition::SelfType(_) |
113 | | Definition::Local(_) | 103 | | Definition::Local(_) |
114 | | Definition::GenericParam(_) | 104 | | Definition::GenericParam(_) |
@@ -1212,7 +1202,7 @@ fn foo<'foo>(_: &'foo ()) { | |||
1212 | } | 1202 | } |
1213 | 1203 | ||
1214 | #[test] | 1204 | #[test] |
1215 | fn goto_def_for_intra_rustdoc_link_same_file() { | 1205 | fn goto_def_for_intra_doc_link_same_file() { |
1216 | check( | 1206 | check( |
1217 | r#" | 1207 | r#" |
1218 | /// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar) | 1208 | /// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar) |
@@ -1225,4 +1215,19 @@ pub fn foo() { } | |||
1225 | }"#, | 1215 | }"#, |
1226 | ) | 1216 | ) |
1227 | } | 1217 | } |
1218 | |||
1219 | #[test] | ||
1220 | fn goto_def_for_intra_doc_link_inner() { | ||
1221 | check( | ||
1222 | r#" | ||
1223 | //- /main.rs | ||
1224 | mod m; | ||
1225 | struct S; | ||
1226 | //^ | ||
1227 | |||
1228 | //- /m.rs | ||
1229 | //! [`super::S$0`] | ||
1230 | "#, | ||
1231 | ) | ||
1232 | } | ||
1228 | } | 1233 | } |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index e892d5588..317b6f011 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -175,12 +175,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov | |||
175 | Definition::SelfType(it) => it.target_ty(db).as_adt(), | 175 | Definition::SelfType(it) => it.target_ty(db).as_adt(), |
176 | _ => None, | 176 | _ => None, |
177 | }?; | 177 | }?; |
178 | match adt { | 178 | adt.try_to_nav(db).map(to_action) |
179 | Adt::Struct(it) => it.try_to_nav(db), | ||
180 | Adt::Union(it) => it.try_to_nav(db), | ||
181 | Adt::Enum(it) => it.try_to_nav(db), | ||
182 | } | ||
183 | .map(to_action) | ||
184 | } | 179 | } |
185 | 180 | ||
186 | fn runnable_action( | 181 | fn runnable_action( |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 099900673..3edc43e08 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -14,16 +14,17 @@ use ide_db::{ | |||
14 | use syntax::{ | 14 | use syntax::{ |
15 | algo::find_node_at_offset, | 15 | algo::find_node_at_offset, |
16 | ast::{self, NameOwner}, | 16 | ast::{self, NameOwner}, |
17 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, | 17 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T, |
18 | }; | 18 | }; |
19 | use test_utils::mark; | 19 | use test_utils::mark; |
20 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
21 | 21 | ||
22 | use crate::{ | 22 | use crate::{ |
23 | references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, | 23 | FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult, |
24 | SourceChange, SourceFileEdit, TextRange, TextSize, | 24 | SourceChange, SourceFileEdit, TextRange, TextSize, |
25 | }; | 25 | }; |
26 | 26 | ||
27 | type RenameResult<T> = Result<T, RenameError>; | ||
27 | #[derive(Debug)] | 28 | #[derive(Debug)] |
28 | pub struct RenameError(pub(crate) String); | 29 | pub struct RenameError(pub(crate) String); |
29 | 30 | ||
@@ -35,24 +36,30 @@ impl fmt::Display for RenameError { | |||
35 | 36 | ||
36 | impl Error for RenameError {} | 37 | impl Error for RenameError {} |
37 | 38 | ||
39 | macro_rules! format_err { | ||
40 | ($fmt:expr) => {RenameError(format!($fmt))}; | ||
41 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | ||
42 | } | ||
43 | |||
44 | macro_rules! bail { | ||
45 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | ||
46 | } | ||
47 | |||
38 | pub(crate) fn prepare_rename( | 48 | pub(crate) fn prepare_rename( |
39 | db: &RootDatabase, | 49 | db: &RootDatabase, |
40 | position: FilePosition, | 50 | position: FilePosition, |
41 | ) -> Result<RangeInfo<()>, RenameError> { | 51 | ) -> RenameResult<RangeInfo<()>> { |
42 | let sema = Semantics::new(db); | 52 | let sema = Semantics::new(db); |
43 | let source_file = sema.parse(position.file_id); | 53 | let source_file = sema.parse(position.file_id); |
44 | let syntax = source_file.syntax(); | 54 | let syntax = source_file.syntax(); |
45 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 55 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { |
46 | rename_mod(&sema, position, module, "dummy") | 56 | rename_mod(&sema, position, module, "dummy") |
47 | } else if let Some(self_token) = | 57 | } else if let Some(self_token) = |
48 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 58 | syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) |
49 | { | 59 | { |
50 | rename_self_to_param(&sema, position, self_token, "dummy") | 60 | rename_self_to_param(&sema, position, self_token, "dummy") |
51 | } else { | 61 | } else { |
52 | let range = match find_all_refs(&sema, position, None) { | 62 | let RangeInfo { range, .. } = find_all_refs(&sema, position)?; |
53 | Some(RangeInfo { range, .. }) => range, | ||
54 | None => return Err(RenameError("No references found at position".to_string())), | ||
55 | }; | ||
56 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) | 63 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) |
57 | } | 64 | } |
58 | .map(|info| RangeInfo::new(info.range, ())) | 65 | .map(|info| RangeInfo::new(info.range, ())) |
@@ -62,7 +69,7 @@ pub(crate) fn rename( | |||
62 | db: &RootDatabase, | 69 | db: &RootDatabase, |
63 | position: FilePosition, | 70 | position: FilePosition, |
64 | new_name: &str, | 71 | new_name: &str, |
65 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 72 | ) -> RenameResult<RangeInfo<SourceChange>> { |
66 | let sema = Semantics::new(db); | 73 | let sema = Semantics::new(db); |
67 | rename_with_semantics(&sema, position, new_name) | 74 | rename_with_semantics(&sema, position, new_name) |
68 | } | 75 | } |
@@ -71,42 +78,18 @@ pub(crate) fn rename_with_semantics( | |||
71 | sema: &Semantics<RootDatabase>, | 78 | sema: &Semantics<RootDatabase>, |
72 | position: FilePosition, | 79 | position: FilePosition, |
73 | new_name: &str, | 80 | new_name: &str, |
74 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 81 | ) -> RenameResult<RangeInfo<SourceChange>> { |
75 | let is_lifetime_name = match lex_single_syntax_kind(new_name) { | ||
76 | Some(res) => match res { | ||
77 | (SyntaxKind::IDENT, _) => false, | ||
78 | (SyntaxKind::UNDERSCORE, _) => false, | ||
79 | (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), | ||
80 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true, | ||
81 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
82 | return Err(RenameError(format!( | ||
83 | "Invalid name `{0}`: Cannot rename lifetime to {0}", | ||
84 | new_name | ||
85 | ))) | ||
86 | } | ||
87 | (_, Some(syntax_error)) => { | ||
88 | return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) | ||
89 | } | ||
90 | (_, None) => { | ||
91 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | ||
92 | } | ||
93 | }, | ||
94 | None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), | ||
95 | }; | ||
96 | |||
97 | let source_file = sema.parse(position.file_id); | 82 | let source_file = sema.parse(position.file_id); |
98 | let syntax = source_file.syntax(); | 83 | let syntax = source_file.syntax(); |
99 | // this is here to prevent lifetime renames from happening on modules and self | 84 | |
100 | if is_lifetime_name { | 85 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { |
101 | rename_reference(&sema, position, new_name, is_lifetime_name) | ||
102 | } else if let Some(module) = find_module_at_offset(&sema, position, syntax) { | ||
103 | rename_mod(&sema, position, module, new_name) | 86 | rename_mod(&sema, position, module, new_name) |
104 | } else if let Some(self_token) = | 87 | } else if let Some(self_token) = |
105 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 88 | syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) |
106 | { | 89 | { |
107 | rename_self_to_param(&sema, position, self_token, new_name) | 90 | rename_self_to_param(&sema, position, self_token, new_name) |
108 | } else { | 91 | } else { |
109 | rename_reference(&sema, position, new_name, is_lifetime_name) | 92 | rename_reference(&sema, position, new_name) |
110 | } | 93 | } |
111 | } | 94 | } |
112 | 95 | ||
@@ -127,6 +110,33 @@ pub(crate) fn will_rename_file( | |||
127 | Some(change) | 110 | Some(change) |
128 | } | 111 | } |
129 | 112 | ||
113 | #[derive(Debug, PartialEq)] | ||
114 | enum IdentifierKind { | ||
115 | Ident, | ||
116 | Lifetime, | ||
117 | ToSelf, | ||
118 | Underscore, | ||
119 | } | ||
120 | |||
121 | fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | ||
122 | match lex_single_syntax_kind(new_name) { | ||
123 | Some(res) => match res { | ||
124 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
125 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
126 | (T![self], _) => Ok(IdentifierKind::ToSelf), | ||
127 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
128 | Ok(IdentifierKind::Lifetime) | ||
129 | } | ||
130 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
131 | bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) | ||
132 | } | ||
133 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
134 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
135 | }, | ||
136 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
137 | } | ||
138 | } | ||
139 | |||
130 | fn find_module_at_offset( | 140 | fn find_module_at_offset( |
131 | sema: &Semantics<RootDatabase>, | 141 | sema: &Semantics<RootDatabase>, |
132 | position: FilePosition, | 142 | position: FilePosition, |
@@ -155,6 +165,14 @@ fn find_module_at_offset( | |||
155 | Some(module) | 165 | Some(module) |
156 | } | 166 | } |
157 | 167 | ||
168 | fn find_all_refs( | ||
169 | sema: &Semantics<RootDatabase>, | ||
170 | position: FilePosition, | ||
171 | ) -> RenameResult<RangeInfo<ReferenceSearchResult>> { | ||
172 | crate::references::find_all_refs(sema, position, None) | ||
173 | .ok_or_else(|| format_err!("No references found at position")) | ||
174 | } | ||
175 | |||
158 | fn source_edit_from_reference( | 176 | fn source_edit_from_reference( |
159 | sema: &Semantics<RootDatabase>, | 177 | sema: &Semantics<RootDatabase>, |
160 | reference: Reference, | 178 | reference: Reference, |
@@ -223,7 +241,10 @@ fn rename_mod( | |||
223 | position: FilePosition, | 241 | position: FilePosition, |
224 | module: Module, | 242 | module: Module, |
225 | new_name: &str, | 243 | new_name: &str, |
226 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 244 | ) -> RenameResult<RangeInfo<SourceChange>> { |
245 | if IdentifierKind::Ident != check_identifier(new_name)? { | ||
246 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
247 | } | ||
227 | let mut source_file_edits = Vec::new(); | 248 | let mut source_file_edits = Vec::new(); |
228 | let mut file_system_edits = Vec::new(); | 249 | let mut file_system_edits = Vec::new(); |
229 | 250 | ||
@@ -254,8 +275,7 @@ fn rename_mod( | |||
254 | source_file_edits.push(edit); | 275 | source_file_edits.push(edit); |
255 | } | 276 | } |
256 | 277 | ||
257 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) | 278 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
258 | .ok_or_else(|| RenameError("No references found at position".to_string()))?; | ||
259 | let ref_edits = refs | 279 | let ref_edits = refs |
260 | .references | 280 | .references |
261 | .into_iter() | 281 | .into_iter() |
@@ -274,27 +294,26 @@ fn rename_to_self( | |||
274 | 294 | ||
275 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) | 295 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) |
276 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) | 296 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) |
277 | .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; | 297 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; |
278 | let param_range = fn_ast | 298 | let param_range = fn_ast |
279 | .param_list() | 299 | .param_list() |
280 | .and_then(|p| p.params().next()) | 300 | .and_then(|p| p.params().next()) |
281 | .ok_or_else(|| RenameError("Method has no parameters".to_string()))? | 301 | .ok_or_else(|| format_err!("Method has no parameters"))? |
282 | .syntax() | 302 | .syntax() |
283 | .text_range(); | 303 | .text_range(); |
284 | if !param_range.contains(position.offset) { | 304 | if !param_range.contains(position.offset) { |
285 | return Err(RenameError("Only the first parameter can be self".to_string())); | 305 | bail!("Only the first parameter can be self"); |
286 | } | 306 | } |
287 | 307 | ||
288 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) | 308 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) |
289 | .and_then(|def| sema.to_def(&def)) | 309 | .and_then(|def| sema.to_def(&def)) |
290 | .ok_or_else(|| RenameError("No impl block found for function".to_string()))?; | 310 | .ok_or_else(|| format_err!("No impl block found for function"))?; |
291 | if fn_def.self_param(sema.db).is_some() { | 311 | if fn_def.self_param(sema.db).is_some() { |
292 | return Err(RenameError("Method already has a self parameter".to_string())); | 312 | bail!("Method already has a self parameter"); |
293 | } | 313 | } |
294 | 314 | ||
295 | let params = fn_def.assoc_fn_params(sema.db); | 315 | let params = fn_def.assoc_fn_params(sema.db); |
296 | let first_param = | 316 | let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?; |
297 | params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?; | ||
298 | let first_param_ty = first_param.ty(); | 317 | let first_param_ty = first_param.ty(); |
299 | let impl_ty = impl_block.target_ty(sema.db); | 318 | let impl_ty = impl_block.target_ty(sema.db); |
300 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { | 319 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { |
@@ -307,18 +326,17 @@ fn rename_to_self( | |||
307 | }; | 326 | }; |
308 | 327 | ||
309 | if ty != impl_ty { | 328 | if ty != impl_ty { |
310 | return Err(RenameError("Parameter type differs from impl block type".to_string())); | 329 | bail!("Parameter type differs from impl block type"); |
311 | } | 330 | } |
312 | 331 | ||
313 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) | 332 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
314 | .ok_or_else(|| RenameError("No reference found at position".to_string()))?; | ||
315 | 333 | ||
316 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs | 334 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs |
317 | .into_iter() | 335 | .into_iter() |
318 | .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); | 336 | .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); |
319 | 337 | ||
320 | if param_ref.is_empty() { | 338 | if param_ref.is_empty() { |
321 | return Err(RenameError("Parameter to rename not found".to_string())); | 339 | bail!("Parameter to rename not found"); |
322 | } | 340 | } |
323 | 341 | ||
324 | let mut edits = usages | 342 | let mut edits = usages |
@@ -367,12 +385,22 @@ fn rename_self_to_param( | |||
367 | self_token: SyntaxToken, | 385 | self_token: SyntaxToken, |
368 | new_name: &str, | 386 | new_name: &str, |
369 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 387 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
388 | let ident_kind = check_identifier(new_name)?; | ||
389 | match ident_kind { | ||
390 | IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), | ||
391 | IdentifierKind::ToSelf => { | ||
392 | // no-op | ||
393 | mark::hit!(rename_self_to_self); | ||
394 | return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default())); | ||
395 | } | ||
396 | _ => (), | ||
397 | } | ||
370 | let source_file = sema.parse(position.file_id); | 398 | let source_file = sema.parse(position.file_id); |
371 | let syn = source_file.syntax(); | 399 | let syn = source_file.syntax(); |
372 | 400 | ||
373 | let text = sema.db.file_text(position.file_id); | 401 | let text = sema.db.file_text(position.file_id); |
374 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) | 402 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) |
375 | .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; | 403 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; |
376 | let search_range = fn_def.syntax().text_range(); | 404 | let search_range = fn_def.syntax().text_range(); |
377 | 405 | ||
378 | let mut edits: Vec<SourceFileEdit> = vec![]; | 406 | let mut edits: Vec<SourceFileEdit> = vec![]; |
@@ -382,12 +410,10 @@ fn rename_self_to_param( | |||
382 | if !search_range.contains_inclusive(offset) { | 410 | if !search_range.contains_inclusive(offset) { |
383 | continue; | 411 | continue; |
384 | } | 412 | } |
385 | if let Some(ref usage) = | 413 | if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) { |
386 | syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
387 | { | ||
388 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { | 414 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { |
389 | text_edit_from_self_param(syn, self_param, new_name) | 415 | text_edit_from_self_param(syn, self_param, new_name) |
390 | .ok_or_else(|| RenameError("No target type found".to_string()))? | 416 | .ok_or_else(|| format_err!("No target type found"))? |
391 | } else { | 417 | } else { |
392 | TextEdit::replace(usage.text_range(), String::from(new_name)) | 418 | TextEdit::replace(usage.text_range(), String::from(new_name)) |
393 | }; | 419 | }; |
@@ -395,6 +421,10 @@ fn rename_self_to_param( | |||
395 | } | 421 | } |
396 | } | 422 | } |
397 | 423 | ||
424 | if edits.len() > 1 && ident_kind == IdentifierKind::Underscore { | ||
425 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
426 | } | ||
427 | |||
398 | let range = ast::SelfParam::cast(self_token.parent()) | 428 | let range = ast::SelfParam::cast(self_token.parent()) |
399 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | 429 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); |
400 | 430 | ||
@@ -405,24 +435,34 @@ fn rename_reference( | |||
405 | sema: &Semantics<RootDatabase>, | 435 | sema: &Semantics<RootDatabase>, |
406 | position: FilePosition, | 436 | position: FilePosition, |
407 | new_name: &str, | 437 | new_name: &str, |
408 | is_lifetime_name: bool, | ||
409 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 438 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
410 | let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { | 439 | let ident_kind = check_identifier(new_name)?; |
411 | Some(range_info) => range_info, | 440 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
412 | None => return Err(RenameError("No references found at position".to_string())), | 441 | |
413 | }; | 442 | match (ident_kind, &refs.declaration.kind) { |
414 | 443 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) | |
415 | match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { | 444 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) |
416 | (true, false) => { | 445 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { |
417 | return Err(RenameError(format!( | 446 | mark::hit!(rename_not_a_lifetime_ident_ref); |
418 | "Invalid name `{}`: not a lifetime identifier", | 447 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
419 | new_name | ||
420 | ))) | ||
421 | } | 448 | } |
422 | (false, true) => { | 449 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), |
423 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | 450 | (IdentifierKind::Lifetime, _) => { |
451 | mark::hit!(rename_not_an_ident_ref); | ||
452 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
424 | } | 453 | } |
425 | _ => (), | 454 | (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => { |
455 | unreachable!("rename_self_to_param should've been called instead") | ||
456 | } | ||
457 | (IdentifierKind::ToSelf, _) => { | ||
458 | mark::hit!(rename_to_self); | ||
459 | return rename_to_self(sema, position); | ||
460 | } | ||
461 | (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { | ||
462 | mark::hit!(rename_underscore_multiple); | ||
463 | bail!("Cannot rename reference to `_` as it is being referenced multiple times") | ||
464 | } | ||
465 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | ||
426 | } | 466 | } |
427 | 467 | ||
428 | let edit = refs | 468 | let edit = refs |
@@ -430,10 +470,6 @@ fn rename_reference( | |||
430 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) | 470 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) |
431 | .collect::<Vec<_>>(); | 471 | .collect::<Vec<_>>(); |
432 | 472 | ||
433 | if edit.is_empty() { | ||
434 | return Err(RenameError("No references found at position".to_string())); | ||
435 | } | ||
436 | |||
437 | Ok(RangeInfo::new(range, SourceChange::from(edit))) | 473 | Ok(RangeInfo::new(range, SourceChange::from(edit))) |
438 | } | 474 | } |
439 | 475 | ||
@@ -462,9 +498,11 @@ mod tests { | |||
462 | text_edit_builder.replace(indel.delete, indel.insert); | 498 | text_edit_builder.replace(indel.delete, indel.insert); |
463 | } | 499 | } |
464 | } | 500 | } |
465 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); | 501 | if let Some(file_id) = file_id { |
466 | text_edit_builder.finish().apply(&mut result); | 502 | let mut result = analysis.file_text(file_id).unwrap().to_string(); |
467 | assert_eq_text!(ra_fixture_after, &*result); | 503 | text_edit_builder.finish().apply(&mut result); |
504 | assert_eq_text!(ra_fixture_after, &*result); | ||
505 | } | ||
468 | } | 506 | } |
469 | Err(err) => { | 507 | Err(err) => { |
470 | if ra_fixture_after.starts_with("error:") { | 508 | if ra_fixture_after.starts_with("error:") { |
@@ -530,6 +568,7 @@ mod tests { | |||
530 | 568 | ||
531 | #[test] | 569 | #[test] |
532 | fn test_rename_to_invalid_identifier_lifetime() { | 570 | fn test_rename_to_invalid_identifier_lifetime() { |
571 | mark::check!(rename_not_an_ident_ref); | ||
533 | check( | 572 | check( |
534 | "'foo", | 573 | "'foo", |
535 | r#"fn main() { let i$0 = 1; }"#, | 574 | r#"fn main() { let i$0 = 1; }"#, |
@@ -539,6 +578,7 @@ mod tests { | |||
539 | 578 | ||
540 | #[test] | 579 | #[test] |
541 | fn test_rename_to_invalid_identifier_lifetime2() { | 580 | fn test_rename_to_invalid_identifier_lifetime2() { |
581 | mark::check!(rename_not_a_lifetime_ident_ref); | ||
542 | check( | 582 | check( |
543 | "foo", | 583 | "foo", |
544 | r#"fn main<'a>(_: &'a$0 ()) {}"#, | 584 | r#"fn main<'a>(_: &'a$0 ()) {}"#, |
@@ -547,7 +587,27 @@ mod tests { | |||
547 | } | 587 | } |
548 | 588 | ||
549 | #[test] | 589 | #[test] |
590 | fn test_rename_to_underscore_invalid() { | ||
591 | mark::check!(rename_underscore_multiple); | ||
592 | check( | ||
593 | "_", | ||
594 | r#"fn main(foo$0: ()) {foo;}"#, | ||
595 | "error: Cannot rename reference to `_` as it is being referenced multiple times", | ||
596 | ); | ||
597 | } | ||
598 | |||
599 | #[test] | ||
600 | fn test_rename_mod_invalid() { | ||
601 | check( | ||
602 | "'foo", | ||
603 | r#"mod foo$0 {}"#, | ||
604 | "error: Invalid name `'foo`: cannot rename module to 'foo", | ||
605 | ); | ||
606 | } | ||
607 | |||
608 | #[test] | ||
550 | fn test_rename_for_local() { | 609 | fn test_rename_for_local() { |
610 | mark::check!(rename_ident); | ||
551 | check( | 611 | check( |
552 | "k", | 612 | "k", |
553 | r#" | 613 | r#" |
@@ -1178,6 +1238,7 @@ fn foo(f: foo::Foo) { | |||
1178 | 1238 | ||
1179 | #[test] | 1239 | #[test] |
1180 | fn test_parameter_to_self() { | 1240 | fn test_parameter_to_self() { |
1241 | mark::check!(rename_to_self); | ||
1181 | check( | 1242 | check( |
1182 | "self", | 1243 | "self", |
1183 | r#" | 1244 | r#" |
@@ -1481,6 +1542,7 @@ fn foo(Foo { i: bar }: foo) -> i32 { | |||
1481 | 1542 | ||
1482 | #[test] | 1543 | #[test] |
1483 | fn test_rename_lifetimes() { | 1544 | fn test_rename_lifetimes() { |
1545 | mark::check!(rename_lifetime); | ||
1484 | check( | 1546 | check( |
1485 | "'yeeee", | 1547 | "'yeeee", |
1486 | r#" | 1548 | r#" |
@@ -1565,4 +1627,24 @@ fn foo<'a>() -> &'a () { | |||
1565 | "#, | 1627 | "#, |
1566 | ) | 1628 | ) |
1567 | } | 1629 | } |
1630 | |||
1631 | #[test] | ||
1632 | fn test_self_to_self() { | ||
1633 | mark::check!(rename_self_to_self); | ||
1634 | check( | ||
1635 | "self", | ||
1636 | r#" | ||
1637 | struct Foo; | ||
1638 | impl Foo { | ||
1639 | fn foo(self$0) {} | ||
1640 | } | ||
1641 | "#, | ||
1642 | r#" | ||
1643 | struct Foo; | ||
1644 | impl Foo { | ||
1645 | fn foo(self) {} | ||
1646 | } | ||
1647 | "#, | ||
1648 | ) | ||
1649 | } | ||
1568 | } | 1650 | } |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 3a1e204db..f5ee7de86 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -3,7 +3,7 @@ use std::fmt; | |||
3 | use assists::utils::test_related_attribute; | 3 | use assists::utils::test_related_attribute; |
4 | use cfg::CfgExpr; | 4 | use cfg::CfgExpr; |
5 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; | 5 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; |
6 | use ide_db::RootDatabase; | 6 | use ide_db::{defs::Definition, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use syntax::{ |
9 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, | 9 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, |
@@ -110,7 +110,10 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | |||
110 | _ => None, | 110 | _ => None, |
111 | } | 111 | } |
112 | }; | 112 | }; |
113 | runnable.or_else(|| runnable_doctest(&sema, item)) | 113 | runnable.or_else(|| match doc_owner_to_def(&sema, item)? { |
114 | Definition::ModuleDef(def) => module_def_doctest(&sema, def), | ||
115 | _ => None, | ||
116 | }) | ||
114 | }) | 117 | }) |
115 | .collect() | 118 | .collect() |
116 | } | 119 | } |
@@ -170,20 +173,26 @@ pub(crate) fn runnable_mod( | |||
170 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) | 173 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) |
171 | } | 174 | } |
172 | 175 | ||
173 | fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { | 176 | // FIXME: figure out a proper API here. |
174 | match_ast! { | 177 | pub(crate) fn doc_owner_to_def( |
178 | sema: &Semantics<RootDatabase>, | ||
179 | item: SyntaxNode, | ||
180 | ) -> Option<Definition> { | ||
181 | let res: hir::ModuleDef = match_ast! { | ||
175 | match item { | 182 | match item { |
176 | ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 183 | ast::SourceFile(it) => sema.scope(&item).module()?.into(), |
177 | ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 184 | ast::Fn(it) => sema.to_def(&it)?.into(), |
178 | ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 185 | ast::Struct(it) => sema.to_def(&it)?.into(), |
179 | ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 186 | ast::Enum(it) => sema.to_def(&it)?.into(), |
180 | ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 187 | ast::Union(it) => sema.to_def(&it)?.into(), |
181 | ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 188 | ast::Trait(it) => sema.to_def(&it)?.into(), |
182 | ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 189 | ast::Const(it) => sema.to_def(&it)?.into(), |
183 | ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), | 190 | ast::Static(it) => sema.to_def(&it)?.into(), |
184 | _ => None, | 191 | ast::TypeAlias(it) => sema.to_def(&it)?.into(), |
192 | _ => return None, | ||
185 | } | 193 | } |
186 | } | 194 | }; |
195 | Some(Definition::ModuleDef(res)) | ||
187 | } | 196 | } |
188 | 197 | ||
189 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { | 198 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { |
diff --git a/docs/dev/style.md b/docs/dev/style.md index 7481f8008..9859f6148 100644 --- a/docs/dev/style.md +++ b/docs/dev/style.md | |||
@@ -78,6 +78,8 @@ Use original span for FileId | |||
78 | 78 | ||
79 | This makes it easier to prepare a changelog. | 79 | This makes it easier to prepare a changelog. |
80 | 80 | ||
81 | If the change adds a new user-visible functionality, consider recording a GIF with [peek](https://github.com/phw/peek) and pasting it into the PR description. | ||
82 | |||
81 | **Rationale:** clean history is potentially useful, but rarely used. | 83 | **Rationale:** clean history is potentially useful, but rarely used. |
82 | But many users read changelogs. | 84 | But many users read changelogs. |
83 | 85 | ||
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 7e5b1f7b5..a957e36af 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs | |||
@@ -85,6 +85,7 @@ fn rust_files_are_tidy() { | |||
85 | for path in rust_files(&project_root().join("crates")) { | 85 | for path in rust_files(&project_root().join("crates")) { |
86 | let text = read_file(&path).unwrap(); | 86 | let text = read_file(&path).unwrap(); |
87 | check_todo(&path, &text); | 87 | check_todo(&path, &text); |
88 | check_dbg(&path, &text); | ||
88 | check_trailing_ws(&path, &text); | 89 | check_trailing_ws(&path, &text); |
89 | deny_clippy(&path, &text); | 90 | deny_clippy(&path, &text); |
90 | tidy_docs.visit(&path, &text); | 91 | tidy_docs.visit(&path, &text); |
@@ -94,10 +95,9 @@ fn rust_files_are_tidy() { | |||
94 | 95 | ||
95 | #[test] | 96 | #[test] |
96 | fn check_merge_commits() { | 97 | fn check_merge_commits() { |
97 | let stdout = | 98 | let stdout = cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19..") |
98 | dbg!(cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19..")) | 99 | .read() |
99 | .read() | 100 | .unwrap(); |
100 | .unwrap(); | ||
101 | if !stdout.is_empty() { | 101 | if !stdout.is_empty() { |
102 | panic!( | 102 | panic!( |
103 | " | 103 | " |
@@ -224,7 +224,7 @@ Zlib OR Apache-2.0 OR MIT | |||
224 | fn check_todo(path: &Path, text: &str) { | 224 | fn check_todo(path: &Path, text: &str) { |
225 | let need_todo = &[ | 225 | let need_todo = &[ |
226 | // This file itself obviously needs to use todo (<- like this!). | 226 | // This file itself obviously needs to use todo (<- like this!). |
227 | "tests/cli.rs", | 227 | "tests/tidy.rs", |
228 | // Some of our assists generate `todo!()`. | 228 | // Some of our assists generate `todo!()`. |
229 | "handlers/add_turbo_fish.rs", | 229 | "handlers/add_turbo_fish.rs", |
230 | "handlers/generate_function.rs", | 230 | "handlers/generate_function.rs", |
@@ -252,6 +252,32 @@ fn check_todo(path: &Path, text: &str) { | |||
252 | } | 252 | } |
253 | } | 253 | } |
254 | 254 | ||
255 | fn check_dbg(path: &Path, text: &str) { | ||
256 | let need_dbg = &[ | ||
257 | // This file itself obviously needs to use dbg. | ||
258 | "tests/tidy.rs", | ||
259 | // Assists to remove `dbg!()` | ||
260 | "handlers/remove_dbg.rs", | ||
261 | // We have .dbg postfix | ||
262 | "completion/src/completions/postfix.rs", | ||
263 | // The documentation in string literals may contain anything for its own purposes | ||
264 | "completion/src/lib.rs", | ||
265 | "completion/src/generated_lint_completions.rs", | ||
266 | // test for doc test for remove_dbg | ||
267 | "src/tests/generated.rs", | ||
268 | ]; | ||
269 | if need_dbg.iter().any(|p| path.ends_with(p)) { | ||
270 | return; | ||
271 | } | ||
272 | if text.contains("dbg!") { | ||
273 | panic!( | ||
274 | "\ndbg! macros should not be committed to the master branch,\n\ | ||
275 | {}\n", | ||
276 | path.display(), | ||
277 | ) | ||
278 | } | ||
279 | } | ||
280 | |||
255 | fn check_trailing_ws(path: &Path, text: &str) { | 281 | fn check_trailing_ws(path: &Path, text: &str) { |
256 | if is_exclude_dir(path, &["test_data"]) { | 282 | if is_exclude_dir(path, &["test_data"]) { |
257 | return; | 283 | return; |