diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/call_hierarchy.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/display/navigation_target.rs | 14 | ||||
-rw-r--r-- | crates/ide/src/doc_links.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 21 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 72 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 676 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlight.rs | 22 | ||||
-rw-r--r-- | crates/ide/src/syntax_tree.rs | 28 |
11 files changed, 430 insertions, 425 deletions
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index e8999a7f3..b10a0a78b 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -47,7 +47,7 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
47 | 47 | ||
48 | let mut calls = CallLocations::default(); | 48 | let mut calls = CallLocations::default(); |
49 | 49 | ||
50 | for (&file_id, references) in refs.info.references().iter() { | 50 | for (&file_id, references) in refs.references().iter() { |
51 | let file = sema.parse(file_id); | 51 | let file = sema.parse(file_id); |
52 | let file = file.syntax(); | 52 | let file = file.syntax(); |
53 | for reference in references { | 53 | for reference in references { |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index e4335119b..579d5a308 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -140,7 +140,7 @@ impl DiagnosticWithFix for IncorrectCase { | |||
140 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; | 140 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; |
141 | 141 | ||
142 | let label = format!("Rename to {}", self.suggested_text); | 142 | let label = format!("Rename to {}", self.suggested_text); |
143 | Some(Fix::new(&label, rename_changes.info, rename_changes.range)) | 143 | Some(Fix::new(&label, rename_changes, frange.range)) |
144 | } | 144 | } |
145 | } | 145 | } |
146 | 146 | ||
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs index 685052e7f..00e601244 100644 --- a/crates/ide/src/display/navigation_target.rs +++ b/crates/ide/src/display/navigation_target.rs | |||
@@ -400,15 +400,13 @@ impl TryToNav for hir::GenericParam { | |||
400 | impl ToNav for hir::Local { | 400 | impl ToNav for hir::Local { |
401 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 401 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
402 | let src = self.source(db); | 402 | let src = self.source(db); |
403 | let (node, focus_range) = match &src.value { | 403 | let (node, name) = match &src.value { |
404 | Either::Left(bind_pat) => ( | 404 | Either::Left(bind_pat) => (bind_pat.syntax().clone(), bind_pat.name()), |
405 | bind_pat.syntax().clone(), | 405 | Either::Right(it) => (it.syntax().clone(), it.name()), |
406 | bind_pat | ||
407 | .name() | ||
408 | .map(|it| src.with_value(&it.syntax().clone()).original_file_range(db).range), | ||
409 | ), | ||
410 | Either::Right(it) => (it.syntax().clone(), it.self_token().map(|it| it.text_range())), | ||
411 | }; | 406 | }; |
407 | let focus_range = | ||
408 | name.map(|it| src.with_value(&it.syntax().clone()).original_file_range(db).range); | ||
409 | |||
412 | let full_range = src.with_value(&node).original_file_range(db); | 410 | let full_range = src.with_value(&node).original_file_range(db); |
413 | let name = match self.name(db) { | 411 | let name = match self.name(db) { |
414 | Some(it) => it.to_string().into(), | 412 | Some(it) => it.to_string().into(), |
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index de10406bc..1f08d7810 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -438,10 +438,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) | |||
438 | FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), | 438 | FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), |
439 | FieldOrAssocItem::AssocItem(assoc) => match assoc { | 439 | FieldOrAssocItem::AssocItem(assoc) => match assoc { |
440 | AssocItem::Function(function) => { | 440 | AssocItem::Function(function) => { |
441 | let is_trait_method = matches!( | 441 | let is_trait_method = function |
442 | function.as_assoc_item(db).map(|assoc| assoc.container(db)), | 442 | .as_assoc_item(db) |
443 | Some(AssocItemContainer::Trait(..)) | 443 | .and_then(|assoc| assoc.containing_trait(db)) |
444 | ); | 444 | .is_some(); |
445 | // This distinction may get more complicated when specialization is available. | 445 | // This distinction may get more complicated when specialization is available. |
446 | // Rustdoc makes this decision based on whether a method 'has defaultness'. | 446 | // Rustdoc makes this decision based on whether a method 'has defaultness'. |
447 | // Currently this is only the case for provided trait methods. | 447 | // Currently this is only the case for provided trait methods. |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 988a5668f..a1d2bce1d 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -55,11 +55,6 @@ pub(crate) fn goto_definition( | |||
55 | } else { | 55 | } else { |
56 | reference_definition(&sema, Either::Left(<)).to_vec() | 56 | reference_definition(&sema, Either::Left(<)).to_vec() |
57 | }, | 57 | }, |
58 | ast::SelfParam(self_param) => { | ||
59 | let def = NameClass::classify_self_param(&sema, &self_param)?.referenced_or_defined(sema.db); | ||
60 | let nav = def.try_to_nav(sema.db)?; | ||
61 | vec![nav] | ||
62 | }, | ||
63 | _ => return None, | 58 | _ => return None, |
64 | } | 59 | } |
65 | }; | 60 | }; |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index ac2d7727e..44ebdbd35 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -98,7 +98,6 @@ pub(crate) fn hover( | |||
98 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), | 98 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
99 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) | 99 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) |
100 | .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), | 100 | .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), |
101 | ast::SelfParam(self_param) => NameClass::classify_self_param(&sema, &self_param).and_then(|d| d.defined(sema.db)), | ||
102 | _ => None, | 101 | _ => None, |
103 | } | 102 | } |
104 | }; | 103 | }; |
@@ -3218,7 +3217,7 @@ impl Foo { | |||
3218 | } | 3217 | } |
3219 | "#, | 3218 | "#, |
3220 | expect![[r#" | 3219 | expect![[r#" |
3221 | *&self* | 3220 | *self* |
3222 | 3221 | ||
3223 | ```rust | 3222 | ```rust |
3224 | &Foo | 3223 | &Foo |
@@ -3238,7 +3237,7 @@ impl Foo { | |||
3238 | } | 3237 | } |
3239 | "#, | 3238 | "#, |
3240 | expect![[r#" | 3239 | expect![[r#" |
3241 | *self: Arc<Foo>* | 3240 | *self* |
3242 | 3241 | ||
3243 | ```rust | 3242 | ```rust |
3244 | Arc<Foo> | 3243 | Arc<Foo> |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index f8d69382e..567b8117e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -86,18 +86,15 @@ pub use completion::{ | |||
86 | InsertTextFormat, | 86 | InsertTextFormat, |
87 | }; | 87 | }; |
88 | pub use hir::{Documentation, Semantics}; | 88 | pub use hir::{Documentation, Semantics}; |
89 | pub use ide_db::base_db::{ | ||
90 | Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot, | ||
91 | SourceRootId, | ||
92 | }; | ||
93 | pub use ide_db::{ | 89 | pub use ide_db::{ |
90 | base_db::{ | ||
91 | Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, | ||
92 | SourceRoot, SourceRootId, | ||
93 | }, | ||
94 | call_info::CallInfo, | 94 | call_info::CallInfo, |
95 | search::{FileReference, ReferenceAccess, ReferenceKind}, | ||
96 | }; | ||
97 | pub use ide_db::{ | ||
98 | label::Label, | 95 | label::Label, |
99 | line_index::{LineCol, LineIndex}, | 96 | line_index::{LineCol, LineIndex}, |
100 | search::SearchScope, | 97 | search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope}, |
101 | source_change::{FileSystemEdit, SourceChange}, | 98 | source_change::{FileSystemEdit, SourceChange}, |
102 | symbol_index::Query, | 99 | symbol_index::Query, |
103 | RootDatabase, | 100 | RootDatabase, |
@@ -372,9 +369,7 @@ impl Analysis { | |||
372 | position: FilePosition, | 369 | position: FilePosition, |
373 | search_scope: Option<SearchScope>, | 370 | search_scope: Option<SearchScope>, |
374 | ) -> Cancelable<Option<ReferenceSearchResult>> { | 371 | ) -> Cancelable<Option<ReferenceSearchResult>> { |
375 | self.with_db(|db| { | 372 | self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope)) |
376 | references::find_all_refs(&Semantics::new(db), position, search_scope).map(|it| it.info) | ||
377 | }) | ||
378 | } | 373 | } |
379 | 374 | ||
380 | /// Finds all methods and free functions for the file. Does not return tests! | 375 | /// Finds all methods and free functions for the file. Does not return tests! |
@@ -481,6 +476,7 @@ impl Analysis { | |||
481 | position: FilePosition, | 476 | position: FilePosition, |
482 | full_import_path: &str, | 477 | full_import_path: &str, |
483 | imported_name: String, | 478 | imported_name: String, |
479 | import_for_trait_assoc_item: bool, | ||
484 | ) -> Cancelable<Vec<TextEdit>> { | 480 | ) -> Cancelable<Vec<TextEdit>> { |
485 | Ok(self | 481 | Ok(self |
486 | .with_db(|db| { | 482 | .with_db(|db| { |
@@ -490,6 +486,7 @@ impl Analysis { | |||
490 | position, | 486 | position, |
491 | full_import_path, | 487 | full_import_path, |
492 | imported_name, | 488 | imported_name, |
489 | import_for_trait_assoc_item, | ||
493 | ) | 490 | ) |
494 | })? | 491 | })? |
495 | .unwrap_or_default()) | 492 | .unwrap_or_default()) |
@@ -523,7 +520,7 @@ impl Analysis { | |||
523 | &self, | 520 | &self, |
524 | position: FilePosition, | 521 | position: FilePosition, |
525 | new_name: &str, | 522 | new_name: &str, |
526 | ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> { | 523 | ) -> Cancelable<Result<SourceChange, RenameError>> { |
527 | self.with_db(|db| references::rename::rename(db, position, new_name)) | 524 | self.with_db(|db| references::rename::rename(db, position, new_name)) |
528 | } | 525 | } |
529 | 526 | ||
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 51a2f4327..3a4f4d80b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -25,7 +25,7 @@ use syntax::{ | |||
25 | AstNode, SyntaxNode, TextRange, TokenAtOffset, T, | 25 | AstNode, SyntaxNode, TextRange, TokenAtOffset, T, |
26 | }; | 26 | }; |
27 | 27 | ||
28 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; | 28 | use crate::{display::TryToNav, FilePosition, NavigationTarget}; |
29 | 29 | ||
30 | #[derive(Debug, Clone)] | 30 | #[derive(Debug, Clone)] |
31 | pub struct ReferenceSearchResult { | 31 | pub struct ReferenceSearchResult { |
@@ -41,14 +41,6 @@ pub struct Declaration { | |||
41 | } | 41 | } |
42 | 42 | ||
43 | impl ReferenceSearchResult { | 43 | impl ReferenceSearchResult { |
44 | pub fn declaration(&self) -> &Declaration { | ||
45 | &self.declaration | ||
46 | } | ||
47 | |||
48 | pub fn decl_target(&self) -> &NavigationTarget { | ||
49 | &self.declaration.nav | ||
50 | } | ||
51 | |||
52 | pub fn references(&self) -> &UsageSearchResult { | 44 | pub fn references(&self) -> &UsageSearchResult { |
53 | &self.references | 45 | &self.references |
54 | } | 46 | } |
@@ -87,7 +79,7 @@ pub(crate) fn find_all_refs( | |||
87 | sema: &Semantics<RootDatabase>, | 79 | sema: &Semantics<RootDatabase>, |
88 | position: FilePosition, | 80 | position: FilePosition, |
89 | search_scope: Option<SearchScope>, | 81 | search_scope: Option<SearchScope>, |
90 | ) -> Option<RangeInfo<ReferenceSearchResult>> { | 82 | ) -> Option<ReferenceSearchResult> { |
91 | let _p = profile::span("find_all_refs"); | 83 | let _p = profile::span("find_all_refs"); |
92 | let syntax = sema.parse(position.file_id).syntax().clone(); | 84 | let syntax = sema.parse(position.file_id).syntax().clone(); |
93 | 85 | ||
@@ -105,7 +97,7 @@ pub(crate) fn find_all_refs( | |||
105 | ) | 97 | ) |
106 | }; | 98 | }; |
107 | 99 | ||
108 | let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; | 100 | let def = find_name(&sema, &syntax, position, opt_name)?; |
109 | 101 | ||
110 | let mut usages = def.usages(sema).set_scope(search_scope).all(); | 102 | let mut usages = def.usages(sema).set_scope(search_scope).all(); |
111 | usages | 103 | usages |
@@ -139,7 +131,7 @@ pub(crate) fn find_all_refs( | |||
139 | 131 | ||
140 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; | 132 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; |
141 | 133 | ||
142 | Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages })) | 134 | Some(ReferenceSearchResult { declaration, references: usages }) |
143 | } | 135 | } |
144 | 136 | ||
145 | fn find_name( | 137 | fn find_name( |
@@ -147,35 +139,27 @@ fn find_name( | |||
147 | syntax: &SyntaxNode, | 139 | syntax: &SyntaxNode, |
148 | position: FilePosition, | 140 | position: FilePosition, |
149 | opt_name: Option<ast::Name>, | 141 | opt_name: Option<ast::Name>, |
150 | ) -> Option<RangeInfo<Definition>> { | 142 | ) -> Option<Definition> { |
151 | if let Some(name) = opt_name { | 143 | let def = if let Some(name) = opt_name { |
152 | let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); | 144 | NameClass::classify(sema, &name)?.referenced_or_defined(sema.db) |
153 | let FileRange { range, .. } = sema.original_range(name.syntax()); | 145 | } else if let Some(lifetime) = |
154 | return Some(RangeInfo::new(range, def)); | ||
155 | } | ||
156 | |||
157 | let (FileRange { range, .. }, def) = if let Some(lifetime) = | ||
158 | sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset) | 146 | sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset) |
159 | { | 147 | { |
160 | if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime) | 148 | if let Some(def) = |
161 | .map(|class| NameRefClass::referenced(class, sema.db)) | 149 | NameRefClass::classify_lifetime(sema, &lifetime).map(|class| class.referenced(sema.db)) |
162 | { | 150 | { |
163 | (sema.original_range(lifetime.syntax()), def) | 151 | def |
164 | } else { | 152 | } else { |
165 | ( | 153 | NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db) |
166 | sema.original_range(lifetime.syntax()), | ||
167 | NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db), | ||
168 | ) | ||
169 | } | 154 | } |
155 | } else if let Some(name_ref) = | ||
156 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset) | ||
157 | { | ||
158 | NameRefClass::classify(sema, &name_ref)?.referenced(sema.db) | ||
170 | } else { | 159 | } else { |
171 | let name_ref = | 160 | return None; |
172 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | ||
173 | ( | ||
174 | sema.original_range(name_ref.syntax()), | ||
175 | NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), | ||
176 | ) | ||
177 | }; | 161 | }; |
178 | Some(RangeInfo::new(range, def)) | 162 | Some(def) |
179 | } | 163 | } |
180 | 164 | ||
181 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { | 165 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { |
@@ -930,6 +914,26 @@ impl Foo { | |||
930 | ); | 914 | ); |
931 | } | 915 | } |
932 | 916 | ||
917 | #[test] | ||
918 | fn test_find_self_refs_decl() { | ||
919 | check( | ||
920 | r#" | ||
921 | struct Foo { bar: i32 } | ||
922 | |||
923 | impl Foo { | ||
924 | fn foo(self$0) { | ||
925 | self; | ||
926 | } | ||
927 | } | ||
928 | "#, | ||
929 | expect![[r#" | ||
930 | self SelfParam FileId(0) 47..51 47..51 SelfParam | ||
931 | |||
932 | FileId(0) 63..67 Other Read | ||
933 | "#]], | ||
934 | ); | ||
935 | } | ||
936 | |||
933 | fn check(ra_fixture: &str, expect: Expect) { | 937 | fn check(ra_fixture: &str, expect: Expect) { |
934 | check_with_scope(ra_fixture, None, expect) | 938 | check_with_scope(ra_fixture, None, expect) |
935 | } | 939 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 9ac4af026..c25bcce50 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -1,27 +1,25 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | use std::{ | 2 | use std::fmt::{self, Display}; |
3 | convert::TryInto, | ||
4 | fmt::{self, Display}, | ||
5 | }; | ||
6 | 3 | ||
7 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use either::Either; |
5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; | ||
8 | use ide_db::{ | 6 | use ide_db::{ |
9 | base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}, | 7 | base_db::{AnchoredPathBuf, FileId, FileRange}, |
10 | defs::{Definition, NameClass, NameRefClass}, | 8 | defs::{Definition, NameClass, NameRefClass}, |
11 | search::FileReference, | 9 | search::FileReference, |
12 | RootDatabase, | 10 | RootDatabase, |
13 | }; | 11 | }; |
12 | use stdx::assert_never; | ||
14 | use syntax::{ | 13 | use syntax::{ |
15 | algo::find_node_at_offset, | ||
16 | ast::{self, NameOwner}, | 14 | ast::{self, NameOwner}, |
17 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T, | 15 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, |
18 | }; | 16 | }; |
19 | use test_utils::mark; | 17 | use test_utils::mark; |
20 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
21 | 19 | ||
22 | use crate::{ | 20 | use crate::{ |
23 | FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, | 21 | display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, SourceChange, |
24 | TextRange, TextSize, | 22 | TextRange, |
25 | }; | 23 | }; |
26 | 24 | ||
27 | type RenameResult<T> = Result<T, RenameError>; | 25 | type RenameResult<T> = Result<T, RenameError>; |
@@ -50,24 +48,22 @@ pub(crate) fn prepare_rename( | |||
50 | let sema = Semantics::new(db); | 48 | let sema = Semantics::new(db); |
51 | let source_file = sema.parse(position.file_id); | 49 | let source_file = sema.parse(position.file_id); |
52 | let syntax = source_file.syntax(); | 50 | let syntax = source_file.syntax(); |
53 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 51 | let range = match &find_name_like(&sema, &syntax, position) |
54 | rename_mod(&sema, position, module, "dummy") | 52 | .ok_or_else(|| format_err!("No references found at position"))? |
55 | } else if let Some(self_token) = | ||
56 | syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) | ||
57 | { | 53 | { |
58 | rename_self_to_param(&sema, position, self_token, "dummy") | 54 | NameLike::Name(it) => it.syntax(), |
59 | } else { | 55 | NameLike::NameRef(it) => it.syntax(), |
60 | let RangeInfo { range, .. } = find_all_refs(&sema, position)?; | 56 | NameLike::Lifetime(it) => it.syntax(), |
61 | Ok(RangeInfo::new(range, SourceChange::default())) | ||
62 | } | 57 | } |
63 | .map(|info| RangeInfo::new(info.range, ())) | 58 | .text_range(); |
59 | Ok(RangeInfo::new(range, ())) | ||
64 | } | 60 | } |
65 | 61 | ||
66 | pub(crate) fn rename( | 62 | pub(crate) fn rename( |
67 | db: &RootDatabase, | 63 | db: &RootDatabase, |
68 | position: FilePosition, | 64 | position: FilePosition, |
69 | new_name: &str, | 65 | new_name: &str, |
70 | ) -> RenameResult<RangeInfo<SourceChange>> { | 66 | ) -> RenameResult<SourceChange> { |
71 | let sema = Semantics::new(db); | 67 | let sema = Semantics::new(db); |
72 | rename_with_semantics(&sema, position, new_name) | 68 | rename_with_semantics(&sema, position, new_name) |
73 | } | 69 | } |
@@ -76,18 +72,15 @@ pub(crate) fn rename_with_semantics( | |||
76 | sema: &Semantics<RootDatabase>, | 72 | sema: &Semantics<RootDatabase>, |
77 | position: FilePosition, | 73 | position: FilePosition, |
78 | new_name: &str, | 74 | new_name: &str, |
79 | ) -> RenameResult<RangeInfo<SourceChange>> { | 75 | ) -> RenameResult<SourceChange> { |
80 | let source_file = sema.parse(position.file_id); | 76 | let source_file = sema.parse(position.file_id); |
81 | let syntax = source_file.syntax(); | 77 | let syntax = source_file.syntax(); |
82 | 78 | ||
83 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 79 | let def = find_definition(sema, syntax, position) |
84 | rename_mod(&sema, position, module, new_name) | 80 | .ok_or_else(|| format_err!("No references found at position"))?; |
85 | } else if let Some(self_token) = | 81 | match def { |
86 | syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) | 82 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), |
87 | { | 83 | def => rename_reference(sema, def, new_name), |
88 | rename_self_to_param(&sema, position, self_token, new_name) | ||
89 | } else { | ||
90 | rename_reference(&sema, position, new_name) | ||
91 | } | 84 | } |
92 | } | 85 | } |
93 | 86 | ||
@@ -98,17 +91,12 @@ pub(crate) fn will_rename_file( | |||
98 | ) -> Option<SourceChange> { | 91 | ) -> Option<SourceChange> { |
99 | let sema = Semantics::new(db); | 92 | let sema = Semantics::new(db); |
100 | let module = sema.to_module_def(file_id)?; | 93 | let module = sema.to_module_def(file_id)?; |
101 | 94 | let mut change = rename_mod(&sema, module, new_name_stem).ok()?; | |
102 | let decl = module.declaration_source(db)?; | ||
103 | let range = decl.value.name()?.syntax().text_range(); | ||
104 | |||
105 | let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() }; | ||
106 | let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info; | ||
107 | change.file_system_edits.clear(); | 95 | change.file_system_edits.clear(); |
108 | Some(change) | 96 | Some(change) |
109 | } | 97 | } |
110 | 98 | ||
111 | #[derive(Debug, PartialEq)] | 99 | #[derive(Copy, Clone, Debug, PartialEq)] |
112 | enum IdentifierKind { | 100 | enum IdentifierKind { |
113 | Ident, | 101 | Ident, |
114 | Lifetime, | 102 | Lifetime, |
@@ -135,40 +123,51 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | |||
135 | } | 123 | } |
136 | } | 124 | } |
137 | 125 | ||
138 | fn find_module_at_offset( | 126 | enum NameLike { |
127 | Name(ast::Name), | ||
128 | NameRef(ast::NameRef), | ||
129 | Lifetime(ast::Lifetime), | ||
130 | } | ||
131 | |||
132 | fn find_name_like( | ||
139 | sema: &Semantics<RootDatabase>, | 133 | sema: &Semantics<RootDatabase>, |
140 | position: FilePosition, | ||
141 | syntax: &SyntaxNode, | 134 | syntax: &SyntaxNode, |
142 | ) -> Option<Module> { | 135 | position: FilePosition, |
143 | let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; | 136 | ) -> Option<NameLike> { |
144 | 137 | let namelike = if let Some(name_ref) = | |
145 | let module = match_ast! { | 138 | sema.find_node_at_offset_with_descend::<ast::NameRef>(syntax, position.offset) |
146 | match (ident.parent()) { | 139 | { |
147 | ast::NameRef(name_ref) => { | 140 | NameLike::NameRef(name_ref) |
148 | match NameRefClass::classify(sema, &name_ref)? { | 141 | } else if let Some(name) = |
149 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 142 | sema.find_node_at_offset_with_descend::<ast::Name>(syntax, position.offset) |
150 | _ => return None, | 143 | { |
151 | } | 144 | NameLike::Name(name) |
152 | }, | 145 | } else if let Some(lifetime) = |
153 | ast::Name(name) => { | 146 | sema.find_node_at_offset_with_descend::<ast::Lifetime>(syntax, position.offset) |
154 | match NameClass::classify(&sema, &name)? { | 147 | { |
155 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 148 | NameLike::Lifetime(lifetime) |
156 | _ => return None, | 149 | } else { |
157 | } | 150 | return None; |
158 | }, | ||
159 | _ => return None, | ||
160 | } | ||
161 | }; | 151 | }; |
162 | 152 | Some(namelike) | |
163 | Some(module) | ||
164 | } | 153 | } |
165 | 154 | ||
166 | fn find_all_refs( | 155 | fn find_definition( |
167 | sema: &Semantics<RootDatabase>, | 156 | sema: &Semantics<RootDatabase>, |
157 | syntax: &SyntaxNode, | ||
168 | position: FilePosition, | 158 | position: FilePosition, |
169 | ) -> RenameResult<RangeInfo<ReferenceSearchResult>> { | 159 | ) -> Option<Definition> { |
170 | crate::references::find_all_refs(sema, position, None) | 160 | let def = match find_name_like(sema, syntax, position)? { |
171 | .ok_or_else(|| format_err!("No references found at position")) | 161 | NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), |
162 | NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), | ||
163 | NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) | ||
164 | .map(|class| NameRefClass::referenced(class, sema.db)) | ||
165 | .or_else(|| { | ||
166 | NameClass::classify_lifetime(sema, &lifetime) | ||
167 | .map(|it| it.referenced_or_defined(sema.db)) | ||
168 | })?, | ||
169 | }; | ||
170 | Some(def) | ||
172 | } | 171 | } |
173 | 172 | ||
174 | fn source_edit_from_references( | 173 | fn source_edit_from_references( |
@@ -242,72 +241,84 @@ fn edit_text_range_for_record_field_expr_or_pat( | |||
242 | 241 | ||
243 | fn rename_mod( | 242 | fn rename_mod( |
244 | sema: &Semantics<RootDatabase>, | 243 | sema: &Semantics<RootDatabase>, |
245 | position: FilePosition, | ||
246 | module: Module, | 244 | module: Module, |
247 | new_name: &str, | 245 | new_name: &str, |
248 | ) -> RenameResult<RangeInfo<SourceChange>> { | 246 | ) -> RenameResult<SourceChange> { |
249 | if IdentifierKind::Ident != check_identifier(new_name)? { | 247 | if IdentifierKind::Ident != check_identifier(new_name)? { |
250 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | 248 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); |
251 | } | 249 | } |
252 | 250 | ||
253 | let mut source_change = SourceChange::default(); | 251 | let mut source_change = SourceChange::default(); |
254 | 252 | ||
255 | let src = module.definition_source(sema.db); | 253 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); |
256 | let file_id = src.file_id.original_file(sema.db); | 254 | let file_id = file_id.original_file(sema.db); |
257 | match src.value { | 255 | if let ModuleSource::SourceFile(..) = def_source { |
258 | ModuleSource::SourceFile(..) => { | 256 | // mod is defined in path/to/dir/mod.rs |
259 | // mod is defined in path/to/dir/mod.rs | 257 | let path = if module.is_mod_rs(sema.db) { |
260 | let path = if module.is_mod_rs(sema.db) { | 258 | format!("../{}/mod.rs", new_name) |
261 | format!("../{}/mod.rs", new_name) | 259 | } else { |
262 | } else { | 260 | format!("{}.rs", new_name) |
263 | format!("{}.rs", new_name) | 261 | }; |
264 | }; | 262 | let dst = AnchoredPathBuf { anchor: file_id, path }; |
265 | let dst = AnchoredPathBuf { anchor: file_id, path }; | 263 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; |
266 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | 264 | source_change.push_file_system_edit(move_file); |
267 | source_change.push_file_system_edit(move_file); | 265 | } |
268 | } | 266 | |
269 | ModuleSource::Module(..) => {} | 267 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { |
270 | } | 268 | let file_id = file_id.original_file(sema.db); |
271 | 269 | match decl_source.name() { | |
272 | if let Some(src) = module.declaration_source(sema.db) { | 270 | Some(name) => source_change.insert_source_edit( |
273 | let file_id = src.file_id.original_file(sema.db); | 271 | file_id, |
274 | let name = src.value.name().unwrap(); | 272 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), |
275 | source_change.insert_source_edit( | 273 | ), |
276 | file_id, | 274 | _ => unreachable!(), |
277 | TextEdit::replace(name.syntax().text_range(), new_name.into()), | 275 | }; |
278 | ); | ||
279 | } | 276 | } |
280 | 277 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | |
281 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 278 | let usages = def.usages(sema).all(); |
282 | let ref_edits = refs.references().iter().map(|(&file_id, references)| { | 279 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
283 | source_edit_from_references(sema, file_id, references, new_name) | 280 | source_edit_from_references(sema, file_id, references, new_name) |
284 | }); | 281 | }); |
285 | source_change.extend(ref_edits); | 282 | source_change.extend(ref_edits); |
286 | 283 | ||
287 | Ok(RangeInfo::new(range, source_change)) | 284 | Ok(source_change) |
288 | } | 285 | } |
289 | 286 | ||
290 | fn rename_to_self( | 287 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
291 | sema: &Semantics<RootDatabase>, | 288 | if assert_never!(local.is_self(sema.db)) { |
292 | position: FilePosition, | 289 | bail!("rename_to_self invoked on self"); |
293 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 290 | } |
294 | let source_file = sema.parse(position.file_id); | 291 | |
295 | let syn = source_file.syntax(); | 292 | let fn_def = match local.parent(sema.db) { |
293 | hir::DefWithBody::Function(func) => func, | ||
294 | _ => bail!("Cannot rename non-param local to self"), | ||
295 | }; | ||
296 | |||
297 | // FIXME: reimplement this on the hir instead | ||
298 | // as of the time of this writing params in hir don't keep their names | ||
299 | let fn_ast = | ||
300 | fn_def.source(sema.db).ok_or(format_err!("Cannot rename non-param local to self"))?.value; | ||
296 | 301 | ||
297 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) | 302 | let first_param_range = fn_ast |
298 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) | ||
299 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | ||
300 | let param_range = fn_ast | ||
301 | .param_list() | 303 | .param_list() |
302 | .and_then(|p| p.params().next()) | 304 | .and_then(|p| p.params().next()) |
303 | .ok_or_else(|| format_err!("Method has no parameters"))? | 305 | .ok_or_else(|| format_err!("Method has no parameters"))? |
304 | .syntax() | 306 | .syntax() |
305 | .text_range(); | 307 | .text_range(); |
306 | if !param_range.contains(position.offset) { | 308 | let InFile { file_id, value: local_source } = local.source(sema.db); |
307 | bail!("Only the first parameter can be self"); | 309 | match local_source { |
310 | either::Either::Left(pat) | ||
311 | if !first_param_range.contains_range(pat.syntax().text_range()) => | ||
312 | { | ||
313 | bail!("Only the first parameter can be self"); | ||
314 | } | ||
315 | _ => (), | ||
308 | } | 316 | } |
309 | 317 | ||
310 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) | 318 | let impl_block = fn_ast |
319 | .syntax() | ||
320 | .ancestors() | ||
321 | .find_map(|node| ast::Impl::cast(node)) | ||
311 | .and_then(|def| sema.to_def(&def)) | 322 | .and_then(|def| sema.to_def(&def)) |
312 | .ok_or_else(|| format_err!("No impl block found for function"))?; | 323 | .ok_or_else(|| format_err!("No impl block found for function"))?; |
313 | if fn_def.self_param(sema.db).is_some() { | 324 | if fn_def.self_param(sema.db).is_some() { |
@@ -331,25 +342,21 @@ fn rename_to_self( | |||
331 | bail!("Parameter type differs from impl block type"); | 342 | bail!("Parameter type differs from impl block type"); |
332 | } | 343 | } |
333 | 344 | ||
334 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 345 | let def = Definition::Local(local); |
335 | 346 | let usages = def.usages(sema).all(); | |
336 | let mut source_change = SourceChange::default(); | 347 | let mut source_change = SourceChange::default(); |
337 | source_change.extend(refs.references().iter().map(|(&file_id, references)| { | 348 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
338 | source_edit_from_references(sema, file_id, references, "self") | 349 | source_edit_from_references(sema, file_id, references, "self") |
339 | })); | 350 | })); |
340 | source_change.insert_source_edit( | 351 | source_change.insert_source_edit( |
341 | position.file_id, | 352 | file_id.original_file(sema.db), |
342 | TextEdit::replace(param_range, String::from(self_param)), | 353 | TextEdit::replace(first_param_range, String::from(self_param)), |
343 | ); | 354 | ); |
344 | 355 | ||
345 | Ok(RangeInfo::new(range, source_change)) | 356 | Ok(source_change) |
346 | } | 357 | } |
347 | 358 | ||
348 | fn text_edit_from_self_param( | 359 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { |
349 | syn: &SyntaxNode, | ||
350 | self_param: &ast::SelfParam, | ||
351 | new_name: &str, | ||
352 | ) -> Option<TextEdit> { | ||
353 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { | 360 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { |
354 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | 361 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { |
355 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | 362 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); |
@@ -357,7 +364,7 @@ fn text_edit_from_self_param( | |||
357 | None | 364 | None |
358 | } | 365 | } |
359 | 366 | ||
360 | let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; | 367 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; |
361 | let type_name = target_type_name(&impl_def)?; | 368 | let type_name = target_type_name(&impl_def)?; |
362 | 369 | ||
363 | let mut replacement_text = String::from(new_name); | 370 | let mut replacement_text = String::from(new_name); |
@@ -374,96 +381,119 @@ fn text_edit_from_self_param( | |||
374 | 381 | ||
375 | fn rename_self_to_param( | 382 | fn rename_self_to_param( |
376 | sema: &Semantics<RootDatabase>, | 383 | sema: &Semantics<RootDatabase>, |
377 | position: FilePosition, | 384 | local: hir::Local, |
378 | self_token: SyntaxToken, | ||
379 | new_name: &str, | 385 | new_name: &str, |
380 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 386 | identifier_kind: IdentifierKind, |
381 | let ident_kind = check_identifier(new_name)?; | 387 | ) -> RenameResult<SourceChange> { |
382 | match ident_kind { | 388 | let (file_id, self_param) = match local.source(sema.db) { |
383 | IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), | 389 | InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param), |
384 | IdentifierKind::ToSelf => { | 390 | _ => { |
385 | // no-op | 391 | assert_never!(true, "rename_self_to_param invoked on a non-self local"); |
386 | mark::hit!(rename_self_to_self); | 392 | bail!("rename_self_to_param invoked on a non-self local"); |
387 | return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default())); | ||
388 | } | ||
389 | _ => (), | ||
390 | } | ||
391 | let source_file = sema.parse(position.file_id); | ||
392 | let syn = source_file.syntax(); | ||
393 | |||
394 | let text = sema.db.file_text(position.file_id); | ||
395 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) | ||
396 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | ||
397 | let search_range = fn_def.syntax().text_range(); | ||
398 | |||
399 | let mut source_change = SourceChange::default(); | ||
400 | |||
401 | for (idx, _) in text.match_indices("self") { | ||
402 | let offset: TextSize = idx.try_into().unwrap(); | ||
403 | if !search_range.contains_inclusive(offset) { | ||
404 | continue; | ||
405 | } | 393 | } |
406 | if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) { | 394 | }; |
407 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { | ||
408 | text_edit_from_self_param(syn, self_param, new_name) | ||
409 | .ok_or_else(|| format_err!("No target type found"))? | ||
410 | } else { | ||
411 | TextEdit::replace(usage.text_range(), String::from(new_name)) | ||
412 | }; | ||
413 | source_change.insert_source_edit(position.file_id, edit); | ||
414 | } | ||
415 | } | ||
416 | 395 | ||
417 | if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore { | 396 | let def = Definition::Local(local); |
397 | let usages = def.usages(sema).all(); | ||
398 | let edit = text_edit_from_self_param(&self_param, new_name) | ||
399 | .ok_or_else(|| format_err!("No target type found"))?; | ||
400 | if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { | ||
418 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | 401 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); |
419 | } | 402 | } |
420 | 403 | let mut source_change = SourceChange::default(); | |
421 | let range = ast::SelfParam::cast(self_token.parent()) | 404 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); |
422 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | 405 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
423 | 406 | source_edit_from_references(sema, file_id, &references, new_name) | |
424 | Ok(RangeInfo::new(range, source_change)) | 407 | })); |
408 | Ok(source_change) | ||
425 | } | 409 | } |
426 | 410 | ||
427 | fn rename_reference( | 411 | fn rename_reference( |
428 | sema: &Semantics<RootDatabase>, | 412 | sema: &Semantics<RootDatabase>, |
429 | position: FilePosition, | 413 | def: Definition, |
430 | new_name: &str, | 414 | new_name: &str, |
431 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 415 | ) -> RenameResult<SourceChange> { |
432 | let ident_kind = check_identifier(new_name)?; | 416 | let ident_kind = check_identifier(new_name)?; |
433 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | ||
434 | 417 | ||
435 | match (ident_kind, &refs.declaration.kind) { | 418 | let def_is_lbl_or_lt = matches!(def, |
436 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) | 419 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) |
437 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) | 420 | | Definition::Label(_) |
438 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { | 421 | ); |
422 | match (ident_kind, def) { | ||
423 | (IdentifierKind::ToSelf, _) | ||
424 | | (IdentifierKind::Underscore, _) | ||
425 | | (IdentifierKind::Ident, _) | ||
426 | if def_is_lbl_or_lt => | ||
427 | { | ||
439 | mark::hit!(rename_not_a_lifetime_ident_ref); | 428 | mark::hit!(rename_not_a_lifetime_ident_ref); |
440 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | 429 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
441 | } | 430 | } |
442 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), | 431 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), |
443 | (IdentifierKind::Lifetime, _) => { | 432 | (IdentifierKind::Lifetime, _) => { |
444 | mark::hit!(rename_not_an_ident_ref); | 433 | mark::hit!(rename_not_an_ident_ref); |
445 | bail!("Invalid name `{}`: not an identifier", new_name) | 434 | bail!("Invalid name `{}`: not an identifier", new_name) |
446 | } | 435 | } |
447 | (IdentifierKind::ToSelf, ReferenceKind::SelfParam) => { | 436 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { |
448 | unreachable!("rename_self_to_param should've been called instead") | 437 | // no-op |
438 | mark::hit!(rename_self_to_self); | ||
439 | return Ok(SourceChange::default()); | ||
449 | } | 440 | } |
450 | (IdentifierKind::ToSelf, _) => { | 441 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { |
451 | mark::hit!(rename_to_self); | 442 | mark::hit!(rename_self_to_param); |
452 | return rename_to_self(sema, position); | 443 | return rename_self_to_param(sema, local, new_name, ident_kind); |
453 | } | 444 | } |
454 | (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { | 445 | (IdentifierKind::ToSelf, Definition::Local(local)) => { |
455 | mark::hit!(rename_underscore_multiple); | 446 | mark::hit!(rename_to_self); |
456 | bail!("Cannot rename reference to `_` as it is being referenced multiple times") | 447 | return rename_to_self(sema, local); |
457 | } | 448 | } |
449 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
458 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | 450 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), |
459 | } | 451 | } |
460 | 452 | ||
453 | let usages = def.usages(sema).all(); | ||
454 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
455 | mark::hit!(rename_underscore_multiple); | ||
456 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
457 | } | ||
461 | let mut source_change = SourceChange::default(); | 458 | let mut source_change = SourceChange::default(); |
462 | source_change.extend(refs.into_iter().map(|(file_id, references)| { | 459 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
463 | source_edit_from_references(sema, file_id, &references, new_name) | 460 | source_edit_from_references(sema, file_id, &references, new_name) |
464 | })); | 461 | })); |
465 | 462 | ||
466 | Ok(RangeInfo::new(range, source_change)) | 463 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; |
464 | source_change.insert_source_edit(file_id, edit); | ||
465 | Ok(source_change) | ||
466 | } | ||
467 | |||
468 | fn source_edit_from_def( | ||
469 | sema: &Semantics<RootDatabase>, | ||
470 | def: Definition, | ||
471 | new_name: &str, | ||
472 | ) -> RenameResult<(FileId, TextEdit)> { | ||
473 | let nav = def.try_to_nav(sema.db).unwrap(); | ||
474 | |||
475 | let mut replacement_text = String::new(); | ||
476 | let mut repl_range = nav.focus_or_full_range(); | ||
477 | if let Definition::Local(local) = def { | ||
478 | if let Either::Left(pat) = local.source(sema.db).value { | ||
479 | if matches!( | ||
480 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
481 | Some(pat_field) if pat_field.name_ref().is_none() | ||
482 | ) { | ||
483 | replacement_text.push_str(": "); | ||
484 | replacement_text.push_str(new_name); | ||
485 | repl_range = TextRange::new( | ||
486 | pat.syntax().text_range().end(), | ||
487 | pat.syntax().text_range().end(), | ||
488 | ); | ||
489 | } | ||
490 | } | ||
491 | } | ||
492 | if replacement_text.is_empty() { | ||
493 | replacement_text.push_str(new_name); | ||
494 | } | ||
495 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
496 | Ok((nav.file_id, edit)) | ||
467 | } | 497 | } |
468 | 498 | ||
469 | #[cfg(test)] | 499 | #[cfg(test)] |
@@ -485,7 +515,7 @@ mod tests { | |||
485 | Ok(source_change) => { | 515 | Ok(source_change) => { |
486 | let mut text_edit_builder = TextEdit::builder(); | 516 | let mut text_edit_builder = TextEdit::builder(); |
487 | let mut file_id: Option<FileId> = None; | 517 | let mut file_id: Option<FileId> = None; |
488 | for edit in source_change.info.source_file_edits { | 518 | for edit in source_change.source_file_edits { |
489 | file_id = Some(edit.0); | 519 | file_id = Some(edit.0); |
490 | for indel in edit.1.into_iter() { | 520 | for indel in edit.1.into_iter() { |
491 | text_edit_builder.replace(indel.delete, indel.insert); | 521 | text_edit_builder.replace(indel.delete, indel.insert); |
@@ -884,36 +914,33 @@ mod foo$0; | |||
884 | // empty | 914 | // empty |
885 | "#, | 915 | "#, |
886 | expect![[r#" | 916 | expect![[r#" |
887 | RangeInfo { | 917 | SourceChange { |
888 | range: 4..7, | 918 | source_file_edits: { |
889 | info: SourceChange { | 919 | FileId( |
890 | source_file_edits: { | 920 | 1, |
891 | FileId( | 921 | ): TextEdit { |
892 | 1, | 922 | indels: [ |
893 | ): TextEdit { | 923 | Indel { |
894 | indels: [ | 924 | insert: "foo2", |
895 | Indel { | 925 | delete: 4..7, |
896 | insert: "foo2", | 926 | }, |
897 | delete: 4..7, | 927 | ], |
898 | }, | ||
899 | ], | ||
900 | }, | ||
901 | }, | 928 | }, |
902 | file_system_edits: [ | 929 | }, |
903 | MoveFile { | 930 | file_system_edits: [ |
904 | src: FileId( | 931 | MoveFile { |
932 | src: FileId( | ||
933 | 2, | ||
934 | ), | ||
935 | dst: AnchoredPathBuf { | ||
936 | anchor: FileId( | ||
905 | 2, | 937 | 2, |
906 | ), | 938 | ), |
907 | dst: AnchoredPathBuf { | 939 | path: "foo2.rs", |
908 | anchor: FileId( | ||
909 | 2, | ||
910 | ), | ||
911 | path: "foo2.rs", | ||
912 | }, | ||
913 | }, | 940 | }, |
914 | ], | 941 | }, |
915 | is_snippet: false, | 942 | ], |
916 | }, | 943 | is_snippet: false, |
917 | } | 944 | } |
918 | "#]], | 945 | "#]], |
919 | ); | 946 | ); |
@@ -936,46 +963,43 @@ pub struct FooContent; | |||
936 | use crate::foo$0::FooContent; | 963 | use crate::foo$0::FooContent; |
937 | "#, | 964 | "#, |
938 | expect![[r#" | 965 | expect![[r#" |
939 | RangeInfo { | 966 | SourceChange { |
940 | range: 11..14, | 967 | source_file_edits: { |
941 | info: SourceChange { | 968 | FileId( |
942 | source_file_edits: { | 969 | 0, |
943 | FileId( | 970 | ): TextEdit { |
944 | 0, | 971 | indels: [ |
945 | ): TextEdit { | 972 | Indel { |
946 | indels: [ | 973 | insert: "quux", |
947 | Indel { | 974 | delete: 8..11, |
948 | insert: "quux", | 975 | }, |
949 | delete: 8..11, | 976 | ], |
950 | }, | 977 | }, |
951 | ], | 978 | FileId( |
952 | }, | 979 | 2, |
953 | FileId( | 980 | ): TextEdit { |
954 | 2, | 981 | indels: [ |
955 | ): TextEdit { | 982 | Indel { |
956 | indels: [ | 983 | insert: "quux", |
957 | Indel { | 984 | delete: 11..14, |
958 | insert: "quux", | 985 | }, |
959 | delete: 11..14, | 986 | ], |
960 | }, | ||
961 | ], | ||
962 | }, | ||
963 | }, | 987 | }, |
964 | file_system_edits: [ | 988 | }, |
965 | MoveFile { | 989 | file_system_edits: [ |
966 | src: FileId( | 990 | MoveFile { |
991 | src: FileId( | ||
992 | 1, | ||
993 | ), | ||
994 | dst: AnchoredPathBuf { | ||
995 | anchor: FileId( | ||
967 | 1, | 996 | 1, |
968 | ), | 997 | ), |
969 | dst: AnchoredPathBuf { | 998 | path: "quux.rs", |
970 | anchor: FileId( | ||
971 | 1, | ||
972 | ), | ||
973 | path: "quux.rs", | ||
974 | }, | ||
975 | }, | 999 | }, |
976 | ], | 1000 | }, |
977 | is_snippet: false, | 1001 | ], |
978 | }, | 1002 | is_snippet: false, |
979 | } | 1003 | } |
980 | "#]], | 1004 | "#]], |
981 | ); | 1005 | ); |
@@ -992,36 +1016,33 @@ mod fo$0o; | |||
992 | // empty | 1016 | // empty |
993 | "#, | 1017 | "#, |
994 | expect![[r#" | 1018 | expect![[r#" |
995 | RangeInfo { | 1019 | SourceChange { |
996 | range: 4..7, | 1020 | source_file_edits: { |
997 | info: SourceChange { | 1021 | FileId( |
998 | source_file_edits: { | 1022 | 0, |
999 | FileId( | 1023 | ): TextEdit { |
1000 | 0, | 1024 | indels: [ |
1001 | ): TextEdit { | 1025 | Indel { |
1002 | indels: [ | 1026 | insert: "foo2", |
1003 | Indel { | 1027 | delete: 4..7, |
1004 | insert: "foo2", | 1028 | }, |
1005 | delete: 4..7, | 1029 | ], |
1006 | }, | ||
1007 | ], | ||
1008 | }, | ||
1009 | }, | 1030 | }, |
1010 | file_system_edits: [ | 1031 | }, |
1011 | MoveFile { | 1032 | file_system_edits: [ |
1012 | src: FileId( | 1033 | MoveFile { |
1034 | src: FileId( | ||
1035 | 1, | ||
1036 | ), | ||
1037 | dst: AnchoredPathBuf { | ||
1038 | anchor: FileId( | ||
1013 | 1, | 1039 | 1, |
1014 | ), | 1040 | ), |
1015 | dst: AnchoredPathBuf { | 1041 | path: "../foo2/mod.rs", |
1016 | anchor: FileId( | ||
1017 | 1, | ||
1018 | ), | ||
1019 | path: "../foo2/mod.rs", | ||
1020 | }, | ||
1021 | }, | 1042 | }, |
1022 | ], | 1043 | }, |
1023 | is_snippet: false, | 1044 | ], |
1024 | }, | 1045 | is_snippet: false, |
1025 | } | 1046 | } |
1026 | "#]], | 1047 | "#]], |
1027 | ); | 1048 | ); |
@@ -1039,36 +1060,33 @@ mod outer { mod fo$0o; } | |||
1039 | // empty | 1060 | // empty |
1040 | "#, | 1061 | "#, |
1041 | expect![[r#" | 1062 | expect![[r#" |
1042 | RangeInfo { | 1063 | SourceChange { |
1043 | range: 16..19, | 1064 | source_file_edits: { |
1044 | info: SourceChange { | 1065 | FileId( |
1045 | source_file_edits: { | 1066 | 0, |
1046 | FileId( | 1067 | ): TextEdit { |
1047 | 0, | 1068 | indels: [ |
1048 | ): TextEdit { | 1069 | Indel { |
1049 | indels: [ | 1070 | insert: "bar", |
1050 | Indel { | 1071 | delete: 16..19, |
1051 | insert: "bar", | 1072 | }, |
1052 | delete: 16..19, | 1073 | ], |
1053 | }, | ||
1054 | ], | ||
1055 | }, | ||
1056 | }, | 1074 | }, |
1057 | file_system_edits: [ | 1075 | }, |
1058 | MoveFile { | 1076 | file_system_edits: [ |
1059 | src: FileId( | 1077 | MoveFile { |
1078 | src: FileId( | ||
1079 | 1, | ||
1080 | ), | ||
1081 | dst: AnchoredPathBuf { | ||
1082 | anchor: FileId( | ||
1060 | 1, | 1083 | 1, |
1061 | ), | 1084 | ), |
1062 | dst: AnchoredPathBuf { | 1085 | path: "bar.rs", |
1063 | anchor: FileId( | ||
1064 | 1, | ||
1065 | ), | ||
1066 | path: "bar.rs", | ||
1067 | }, | ||
1068 | }, | 1086 | }, |
1069 | ], | 1087 | }, |
1070 | is_snippet: false, | 1088 | ], |
1071 | }, | 1089 | is_snippet: false, |
1072 | } | 1090 | } |
1073 | "#]], | 1091 | "#]], |
1074 | ); | 1092 | ); |
@@ -1109,46 +1127,43 @@ pub mod foo$0; | |||
1109 | // pub fn fun() {} | 1127 | // pub fn fun() {} |
1110 | "#, | 1128 | "#, |
1111 | expect![[r#" | 1129 | expect![[r#" |
1112 | RangeInfo { | 1130 | SourceChange { |
1113 | range: 8..11, | 1131 | source_file_edits: { |
1114 | info: SourceChange { | 1132 | FileId( |
1115 | source_file_edits: { | 1133 | 0, |
1116 | FileId( | 1134 | ): TextEdit { |
1117 | 0, | 1135 | indels: [ |
1118 | ): TextEdit { | 1136 | Indel { |
1119 | indels: [ | 1137 | insert: "foo2", |
1120 | Indel { | 1138 | delete: 27..30, |
1121 | insert: "foo2", | 1139 | }, |
1122 | delete: 27..30, | 1140 | ], |
1123 | }, | ||
1124 | ], | ||
1125 | }, | ||
1126 | FileId( | ||
1127 | 1, | ||
1128 | ): TextEdit { | ||
1129 | indels: [ | ||
1130 | Indel { | ||
1131 | insert: "foo2", | ||
1132 | delete: 8..11, | ||
1133 | }, | ||
1134 | ], | ||
1135 | }, | ||
1136 | }, | 1141 | }, |
1137 | file_system_edits: [ | 1142 | FileId( |
1138 | MoveFile { | 1143 | 1, |
1139 | src: FileId( | 1144 | ): TextEdit { |
1145 | indels: [ | ||
1146 | Indel { | ||
1147 | insert: "foo2", | ||
1148 | delete: 8..11, | ||
1149 | }, | ||
1150 | ], | ||
1151 | }, | ||
1152 | }, | ||
1153 | file_system_edits: [ | ||
1154 | MoveFile { | ||
1155 | src: FileId( | ||
1156 | 2, | ||
1157 | ), | ||
1158 | dst: AnchoredPathBuf { | ||
1159 | anchor: FileId( | ||
1140 | 2, | 1160 | 2, |
1141 | ), | 1161 | ), |
1142 | dst: AnchoredPathBuf { | 1162 | path: "foo2.rs", |
1143 | anchor: FileId( | ||
1144 | 2, | ||
1145 | ), | ||
1146 | path: "foo2.rs", | ||
1147 | }, | ||
1148 | }, | 1163 | }, |
1149 | ], | 1164 | }, |
1150 | is_snippet: false, | 1165 | ], |
1151 | }, | 1166 | is_snippet: false, |
1152 | } | 1167 | } |
1153 | "#]], | 1168 | "#]], |
1154 | ); | 1169 | ); |
@@ -1350,6 +1365,7 @@ impl Foo { | |||
1350 | 1365 | ||
1351 | #[test] | 1366 | #[test] |
1352 | fn test_owned_self_to_parameter() { | 1367 | fn test_owned_self_to_parameter() { |
1368 | mark::check!(rename_self_to_param); | ||
1353 | check( | 1369 | check( |
1354 | "foo", | 1370 | "foo", |
1355 | r#" | 1371 | r#" |
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 87578e70a..8625ef5df 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -68,7 +68,8 @@ pub(super) fn element( | |||
68 | NAME_REF => { | 68 | NAME_REF => { |
69 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 69 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
70 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | 70 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { |
71 | match NameRefClass::classify(sema, &name_ref) { | 71 | let is_self = name_ref.self_token().is_some(); |
72 | let h = match NameRefClass::classify(sema, &name_ref) { | ||
72 | Some(name_kind) => match name_kind { | 73 | Some(name_kind) => match name_kind { |
73 | NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), | 74 | NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), |
74 | NameRefClass::Definition(def) => { | 75 | NameRefClass::Definition(def) => { |
@@ -108,6 +109,11 @@ pub(super) fn element( | |||
108 | highlight_name_ref_by_syntax(name_ref, sema) | 109 | highlight_name_ref_by_syntax(name_ref, sema) |
109 | } | 110 | } |
110 | None => HlTag::UnresolvedReference.into(), | 111 | None => HlTag::UnresolvedReference.into(), |
112 | }; | ||
113 | if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self { | ||
114 | HlTag::Symbol(SymbolKind::SelfParam).into() | ||
115 | } else { | ||
116 | h | ||
111 | } | 117 | } |
112 | }) | 118 | }) |
113 | } | 119 | } |
@@ -225,18 +231,8 @@ pub(super) fn element( | |||
225 | T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, | 231 | T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, |
226 | T![unsafe] => h | HlMod::Unsafe, | 232 | T![unsafe] => h | HlMod::Unsafe, |
227 | T![true] | T![false] => HlTag::BoolLiteral.into(), | 233 | T![true] | T![false] => HlTag::BoolLiteral.into(), |
228 | T![self] => { | 234 | // self is handled as either a Name or NameRef already |
229 | let self_param = element.parent().and_then(ast::SelfParam::cast); | 235 | T![self] => return None, |
230 | if let Some(NameClass::Definition(def)) = self_param | ||
231 | .and_then(|self_param| NameClass::classify_self_param(sema, &self_param)) | ||
232 | { | ||
233 | highlight_def(db, def) | HlMod::Definition | ||
234 | } else if element.ancestors().any(|it| it.kind() == USE_TREE) { | ||
235 | HlTag::Symbol(SymbolKind::SelfParam).into() | ||
236 | } else { | ||
237 | return None; | ||
238 | } | ||
239 | } | ||
240 | T![ref] => element | 236 | T![ref] => element |
241 | .parent() | 237 | .parent() |
242 | .and_then(ast::IdentPat::cast) | 238 | .and_then(ast::IdentPat::cast) |
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs index 1d4bac7ad..4c63d3023 100644 --- a/crates/ide/src/syntax_tree.rs +++ b/crates/ide/src/syntax_tree.rs | |||
@@ -111,7 +111,6 @@ mod tests { | |||
111 | let syn = analysis.syntax_tree(file_id, None).unwrap(); | 111 | let syn = analysis.syntax_tree(file_id, None).unwrap(); |
112 | 112 | ||
113 | assert_eq_text!( | 113 | assert_eq_text!( |
114 | syn.trim(), | ||
115 | r#" | 114 | r#" |
116 | [email protected] | 115 | [email protected] |
117 | [email protected] | 116 | [email protected] |
@@ -127,7 +126,8 @@ [email protected] | |||
127 | [email protected] "{" | 126 | [email protected] "{" |
128 | [email protected] "}" | 127 | [email protected] "}" |
129 | "# | 128 | "# |
130 | .trim() | 129 | .trim(), |
130 | syn.trim() | ||
131 | ); | 131 | ); |
132 | 132 | ||
133 | let (analysis, file_id) = fixture::file( | 133 | let (analysis, file_id) = fixture::file( |
@@ -143,7 +143,6 @@ fn test() { | |||
143 | let syn = analysis.syntax_tree(file_id, None).unwrap(); | 143 | let syn = analysis.syntax_tree(file_id, None).unwrap(); |
144 | 144 | ||
145 | assert_eq_text!( | 145 | assert_eq_text!( |
146 | syn.trim(), | ||
147 | r#" | 146 | r#" |
148 | [email protected] | 147 | [email protected] |
149 | [email protected] | 148 | [email protected] |
@@ -176,7 +175,8 @@ [email protected] | |||
176 | [email protected] "\n" | 175 | [email protected] "\n" |
177 | [email protected] "}" | 176 | [email protected] "}" |
178 | "# | 177 | "# |
179 | .trim() | 178 | .trim(), |
179 | syn.trim() | ||
180 | ); | 180 | ); |
181 | } | 181 | } |
182 | 182 | ||
@@ -186,7 +186,6 @@ [email protected] | |||
186 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 186 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); |
187 | 187 | ||
188 | assert_eq_text!( | 188 | assert_eq_text!( |
189 | syn.trim(), | ||
190 | r#" | 189 | r#" |
191 | [email protected] | 190 | [email protected] |
192 | [email protected] "fn" | 191 | [email protected] "fn" |
@@ -201,7 +200,8 @@ [email protected] | |||
201 | [email protected] "{" | 200 | [email protected] "{" |
202 | [email protected] "}" | 201 | [email protected] "}" |
203 | "# | 202 | "# |
204 | .trim() | 203 | .trim(), |
204 | syn.trim() | ||
205 | ); | 205 | ); |
206 | 206 | ||
207 | let (analysis, range) = fixture::range( | 207 | let (analysis, range) = fixture::range( |
@@ -216,7 +216,6 @@ [email protected] | |||
216 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 216 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); |
217 | 217 | ||
218 | assert_eq_text!( | 218 | assert_eq_text!( |
219 | syn.trim(), | ||
220 | r#" | 219 | r#" |
221 | [email protected] | 220 | [email protected] |
222 | [email protected] | 221 | [email protected] |
@@ -234,7 +233,8 @@ [email protected] | |||
234 | [email protected] ")" | 233 | [email protected] ")" |
235 | [email protected] ";" | 234 | [email protected] ";" |
236 | "# | 235 | "# |
237 | .trim() | 236 | .trim(), |
237 | syn.trim() | ||
238 | ); | 238 | ); |
239 | } | 239 | } |
240 | 240 | ||
@@ -253,7 +253,6 @@ fn bar() { | |||
253 | ); | 253 | ); |
254 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 254 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); |
255 | assert_eq_text!( | 255 | assert_eq_text!( |
256 | syn.trim(), | ||
257 | r#" | 256 | r#" |
258 | [email protected] | 257 | [email protected] |
259 | [email protected] | 258 | [email protected] |
@@ -270,7 +269,8 @@ [email protected] | |||
270 | [email protected] "\n" | 269 | [email protected] "\n" |
271 | [email protected] "}" | 270 | [email protected] "}" |
272 | "# | 271 | "# |
273 | .trim() | 272 | .trim(), |
273 | syn.trim() | ||
274 | ); | 274 | ); |
275 | 275 | ||
276 | // With a raw string | 276 | // With a raw string |
@@ -287,7 +287,6 @@ fn bar() { | |||
287 | ); | 287 | ); |
288 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 288 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); |
289 | assert_eq_text!( | 289 | assert_eq_text!( |
290 | syn.trim(), | ||
291 | r#" | 290 | r#" |
292 | [email protected] | 291 | [email protected] |
293 | [email protected] | 292 | [email protected] |
@@ -304,7 +303,8 @@ [email protected] | |||
304 | [email protected] "\n" | 303 | [email protected] "\n" |
305 | [email protected] "}" | 304 | [email protected] "}" |
306 | "# | 305 | "# |
307 | .trim() | 306 | .trim(), |
307 | syn.trim() | ||
308 | ); | 308 | ); |
309 | 309 | ||
310 | // With a raw string | 310 | // With a raw string |
@@ -320,7 +320,6 @@ fn bar() { | |||
320 | ); | 320 | ); |
321 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); | 321 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); |
322 | assert_eq_text!( | 322 | assert_eq_text!( |
323 | syn.trim(), | ||
324 | r#" | 323 | r#" |
325 | [email protected] | 324 | [email protected] |
326 | [email protected] | 325 | [email protected] |
@@ -351,7 +350,8 @@ [email protected] | |||
351 | [email protected] "\n" | 350 | [email protected] "\n" |
352 | [email protected] "}" | 351 | [email protected] "}" |
353 | "# | 352 | "# |
354 | .trim() | 353 | .trim(), |
354 | syn.trim() | ||
355 | ); | 355 | ); |
356 | } | 356 | } |
357 | } | 357 | } |