diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/base_db/src/fixture.rs | 8 | ||||
-rw-r--r-- | crates/hir_ty/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ide/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ide/src/fixture.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 21 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/rename.rs (renamed from crates/ide/src/references/rename.rs) | 459 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 163 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 26 | ||||
-rw-r--r-- | crates/ide_db/src/assists.rs | 136 | ||||
-rw-r--r-- | crates/ide_db/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ide_db/src/rename.rs | 468 | ||||
-rw-r--r-- | crates/ide_diagnostics/Cargo.toml | 29 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/field_shorthand.rs (renamed from crates/ide/src/diagnostics/field_shorthand.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/break_outside_of_loop.rs (renamed from crates/ide/src/diagnostics/break_outside_of_loop.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/inactive_code.rs (renamed from crates/ide/src/diagnostics/inactive_code.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/incorrect_case.rs (renamed from crates/ide/src/diagnostics/incorrect_case.rs) | 43 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/macro_error.rs (renamed from crates/ide/src/diagnostics/macro_error.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs (renamed from crates/ide/src/diagnostics/mismatched_arg_count.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/missing_fields.rs (renamed from crates/ide/src/diagnostics/missing_fields.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/missing_match_arms.rs (renamed from crates/ide/src/diagnostics/missing_match_arms.rs) | 10 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs (renamed from crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/missing_unsafe.rs (renamed from crates/ide/src/diagnostics/missing_unsafe.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/no_such_field.rs (renamed from crates/ide/src/diagnostics/no_such_field.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs (renamed from crates/ide/src/diagnostics/remove_this_semicolon.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs (renamed from crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs) | 11 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unimplemented_builtin_macro.rs (renamed from crates/ide/src/diagnostics/unimplemented_builtin_macro.rs) | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unlinked_file.rs (renamed from crates/ide/src/diagnostics/unlinked_file.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unresolved_extern_crate.rs (renamed from crates/ide/src/diagnostics/unresolved_extern_crate.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unresolved_import.rs (renamed from crates/ide/src/diagnostics/unresolved_import.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unresolved_macro_call.rs (renamed from crates/ide/src/diagnostics/unresolved_macro_call.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unresolved_module.rs (renamed from crates/ide/src/diagnostics/unresolved_module.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/handlers/unresolved_proc_macro.rs (renamed from crates/ide/src/diagnostics/unresolved_proc_macro.rs) | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/lib.rs (renamed from crates/ide/src/diagnostics.rs) | 202 |
34 files changed, 918 insertions, 799 deletions
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs index da4afb5eb..1b17db102 100644 --- a/crates/base_db/src/fixture.rs +++ b/crates/base_db/src/fixture.rs | |||
@@ -24,6 +24,14 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static { | |||
24 | (db, fixture.files[0]) | 24 | (db, fixture.files[0]) |
25 | } | 25 | } |
26 | 26 | ||
27 | fn with_many_files(ra_fixture: &str) -> (Self, Vec<FileId>) { | ||
28 | let fixture = ChangeFixture::parse(ra_fixture); | ||
29 | let mut db = Self::default(); | ||
30 | fixture.change.apply(&mut db); | ||
31 | assert!(fixture.file_position.is_none()); | ||
32 | (db, fixture.files) | ||
33 | } | ||
34 | |||
27 | fn with_files(ra_fixture: &str) -> Self { | 35 | fn with_files(ra_fixture: &str) -> Self { |
28 | let fixture = ChangeFixture::parse(ra_fixture); | 36 | let fixture = ChangeFixture::parse(ra_fixture); |
29 | let mut db = Self::default(); | 37 | let mut db = Self::default(); |
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml index a1894e8d8..74129eb21 100644 --- a/crates/hir_ty/Cargo.toml +++ b/crates/hir_ty/Cargo.toml | |||
@@ -20,7 +20,7 @@ rustc-hash = "1.1.0" | |||
20 | scoped-tls = "1" | 20 | scoped-tls = "1" |
21 | chalk-solve = { version = "0.68", default-features = false } | 21 | chalk-solve = { version = "0.68", default-features = false } |
22 | chalk-ir = "0.68" | 22 | chalk-ir = "0.68" |
23 | chalk-recursive = "0.68" | 23 | chalk-recursive = { version = "0.68", default-features = false } |
24 | la-arena = { version = "0.2.0", path = "../../lib/arena" } | 24 | la-arena = { version = "0.2.0", path = "../../lib/arena" } |
25 | once_cell = { version = "1.5.0" } | 25 | once_cell = { version = "1.5.0" } |
26 | 26 | ||
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index f12928225..0e8447394 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml | |||
@@ -29,6 +29,7 @@ ide_db = { path = "../ide_db", version = "0.0.0" } | |||
29 | cfg = { path = "../cfg", version = "0.0.0" } | 29 | cfg = { path = "../cfg", version = "0.0.0" } |
30 | profile = { path = "../profile", version = "0.0.0" } | 30 | profile = { path = "../profile", version = "0.0.0" } |
31 | ide_assists = { path = "../ide_assists", version = "0.0.0" } | 31 | ide_assists = { path = "../ide_assists", version = "0.0.0" } |
32 | ide_diagnostics = { path = "../ide_diagnostics", version = "0.0.0" } | ||
32 | ide_ssr = { path = "../ide_ssr", version = "0.0.0" } | 33 | ide_ssr = { path = "../ide_ssr", version = "0.0.0" } |
33 | ide_completion = { path = "../ide_completion", version = "0.0.0" } | 34 | ide_completion = { path = "../ide_completion", version = "0.0.0" } |
34 | 35 | ||
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs index 38e2e866b..cf679edd3 100644 --- a/crates/ide/src/fixture.rs +++ b/crates/ide/src/fixture.rs | |||
@@ -12,14 +12,6 @@ pub(crate) fn file(ra_fixture: &str) -> (Analysis, FileId) { | |||
12 | (host.analysis(), change_fixture.files[0]) | 12 | (host.analysis(), change_fixture.files[0]) |
13 | } | 13 | } |
14 | 14 | ||
15 | /// Creates analysis for many files. | ||
16 | pub(crate) fn files(ra_fixture: &str) -> (Analysis, Vec<FileId>) { | ||
17 | let mut host = AnalysisHost::default(); | ||
18 | let change_fixture = ChangeFixture::parse(ra_fixture); | ||
19 | host.db.apply_change(change_fixture.change); | ||
20 | (host.analysis(), change_fixture.files) | ||
21 | } | ||
22 | |||
23 | /// Creates analysis from a multi-file fixture, returns positions marked with $0. | 15 | /// Creates analysis from a multi-file fixture, returns positions marked with $0. |
24 | pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) { | 16 | pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) { |
25 | let mut host = AnalysisHost::default(); | 17 | let mut host = AnalysisHost::default(); |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0511efae3..9db387d26 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -24,7 +24,6 @@ mod display; | |||
24 | 24 | ||
25 | mod annotations; | 25 | mod annotations; |
26 | mod call_hierarchy; | 26 | mod call_hierarchy; |
27 | mod diagnostics; | ||
28 | mod expand_macro; | 27 | mod expand_macro; |
29 | mod extend_selection; | 28 | mod extend_selection; |
30 | mod file_structure; | 29 | mod file_structure; |
@@ -40,6 +39,7 @@ mod matching_brace; | |||
40 | mod move_item; | 39 | mod move_item; |
41 | mod parent_module; | 40 | mod parent_module; |
42 | mod references; | 41 | mod references; |
42 | mod rename; | ||
43 | mod fn_references; | 43 | mod fn_references; |
44 | mod runnables; | 44 | mod runnables; |
45 | mod ssr; | 45 | mod ssr; |
@@ -71,7 +71,6 @@ use crate::display::ToNav; | |||
71 | pub use crate::{ | 71 | pub use crate::{ |
72 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, | 72 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, |
73 | call_hierarchy::CallItem, | 73 | call_hierarchy::CallItem, |
74 | diagnostics::{Diagnostic, DiagnosticsConfig, Severity}, | ||
75 | display::navigation_target::NavigationTarget, | 74 | display::navigation_target::NavigationTarget, |
76 | expand_macro::ExpandedMacro, | 75 | expand_macro::ExpandedMacro, |
77 | file_structure::{StructureNode, StructureNodeKind}, | 76 | file_structure::{StructureNode, StructureNodeKind}, |
@@ -81,7 +80,8 @@ pub use crate::{ | |||
81 | markup::Markup, | 80 | markup::Markup, |
82 | move_item::Direction, | 81 | move_item::Direction, |
83 | prime_caches::PrimeCachesProgress, | 82 | prime_caches::PrimeCachesProgress, |
84 | references::{rename::RenameError, ReferenceSearchResult}, | 83 | references::ReferenceSearchResult, |
84 | rename::RenameError, | ||
85 | runnables::{Runnable, RunnableKind, TestId}, | 85 | runnables::{Runnable, RunnableKind, TestId}, |
86 | syntax_highlighting::{ | 86 | syntax_highlighting::{ |
87 | tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, | 87 | tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, |
@@ -109,6 +109,7 @@ pub use ide_db::{ | |||
109 | symbol_index::Query, | 109 | symbol_index::Query, |
110 | RootDatabase, SymbolKind, | 110 | RootDatabase, SymbolKind, |
111 | }; | 111 | }; |
112 | pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, Severity}; | ||
112 | pub use ide_ssr::SsrError; | 113 | pub use ide_ssr::SsrError; |
113 | pub use syntax::{TextRange, TextSize}; | 114 | pub use syntax::{TextRange, TextSize}; |
114 | pub use text_edit::{Indel, TextEdit}; | 115 | pub use text_edit::{Indel, TextEdit}; |
@@ -536,7 +537,7 @@ impl Analysis { | |||
536 | ) -> Cancellable<Vec<Assist>> { | 537 | ) -> Cancellable<Vec<Assist>> { |
537 | self.with_db(|db| { | 538 | self.with_db(|db| { |
538 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | 539 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); |
539 | let mut acc = Assist::get(db, config, resolve, frange); | 540 | let mut acc = ide_assists::assists(db, config, resolve, frange); |
540 | acc.extend(ssr_assists.into_iter()); | 541 | acc.extend(ssr_assists.into_iter()); |
541 | acc | 542 | acc |
542 | }) | 543 | }) |
@@ -549,7 +550,7 @@ impl Analysis { | |||
549 | resolve: AssistResolveStrategy, | 550 | resolve: AssistResolveStrategy, |
550 | file_id: FileId, | 551 | file_id: FileId, |
551 | ) -> Cancellable<Vec<Diagnostic>> { | 552 | ) -> Cancellable<Vec<Diagnostic>> { |
552 | self.with_db(|db| diagnostics::diagnostics(db, config, &resolve, file_id)) | 553 | self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id)) |
553 | } | 554 | } |
554 | 555 | ||
555 | /// Convenience function to return assists + quick fixes for diagnostics | 556 | /// Convenience function to return assists + quick fixes for diagnostics |
@@ -568,7 +569,7 @@ impl Analysis { | |||
568 | self.with_db(|db| { | 569 | self.with_db(|db| { |
569 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | 570 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); |
570 | let diagnostic_assists = if include_fixes { | 571 | let diagnostic_assists = if include_fixes { |
571 | diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) | 572 | ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) |
572 | .into_iter() | 573 | .into_iter() |
573 | .flat_map(|it| it.fixes.unwrap_or_default()) | 574 | .flat_map(|it| it.fixes.unwrap_or_default()) |
574 | .filter(|it| it.target.intersect(frange.range).is_some()) | 575 | .filter(|it| it.target.intersect(frange.range).is_some()) |
@@ -577,7 +578,7 @@ impl Analysis { | |||
577 | Vec::new() | 578 | Vec::new() |
578 | }; | 579 | }; |
579 | 580 | ||
580 | let mut res = Assist::get(db, assist_config, resolve, frange); | 581 | let mut res = ide_assists::assists(db, assist_config, resolve, frange); |
581 | res.extend(ssr_assists.into_iter()); | 582 | res.extend(ssr_assists.into_iter()); |
582 | res.extend(diagnostic_assists.into_iter()); | 583 | res.extend(diagnostic_assists.into_iter()); |
583 | 584 | ||
@@ -592,14 +593,14 @@ impl Analysis { | |||
592 | position: FilePosition, | 593 | position: FilePosition, |
593 | new_name: &str, | 594 | new_name: &str, |
594 | ) -> Cancellable<Result<SourceChange, RenameError>> { | 595 | ) -> Cancellable<Result<SourceChange, RenameError>> { |
595 | self.with_db(|db| references::rename::rename(db, position, new_name)) | 596 | self.with_db(|db| rename::rename(db, position, new_name)) |
596 | } | 597 | } |
597 | 598 | ||
598 | pub fn prepare_rename( | 599 | pub fn prepare_rename( |
599 | &self, | 600 | &self, |
600 | position: FilePosition, | 601 | position: FilePosition, |
601 | ) -> Cancellable<Result<RangeInfo<()>, RenameError>> { | 602 | ) -> Cancellable<Result<RangeInfo<()>, RenameError>> { |
602 | self.with_db(|db| references::rename::prepare_rename(db, position)) | 603 | self.with_db(|db| rename::prepare_rename(db, position)) |
603 | } | 604 | } |
604 | 605 | ||
605 | pub fn will_rename_file( | 606 | pub fn will_rename_file( |
@@ -607,7 +608,7 @@ impl Analysis { | |||
607 | file_id: FileId, | 608 | file_id: FileId, |
608 | new_name_stem: &str, | 609 | new_name_stem: &str, |
609 | ) -> Cancellable<Option<SourceChange>> { | 610 | ) -> Cancellable<Option<SourceChange>> { |
610 | self.with_db(|db| references::rename::will_rename_file(db, file_id, new_name_stem)) | 611 | self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem)) |
611 | } | 612 | } |
612 | 613 | ||
613 | pub fn structural_search_replace( | 614 | pub fn structural_search_replace( |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index a0fdead2c..945c9b9e1 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -9,8 +9,6 @@ | |||
9 | //! at the index that the match starts at and its tree parent is | 9 | //! at the index that the match starts at and its tree parent is |
10 | //! resolved to the search element definition, we get a reference. | 10 | //! resolved to the search element definition, we get a reference. |
11 | 11 | ||
12 | pub(crate) mod rename; | ||
13 | |||
14 | use hir::{PathResolution, Semantics}; | 12 | use hir::{PathResolution, Semantics}; |
15 | use ide_db::{ | 13 | use ide_db::{ |
16 | base_db::FileId, | 14 | base_db::FileId, |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/rename.rs index 6b3d02bf4..8096dfa0e 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/rename.rs | |||
@@ -1,45 +1,25 @@ | |||
1 | //! Renaming functionality | 1 | //! Renaming functionality. |
2 | //! | 2 | //! |
3 | //! All reference and file rename requests go through here where the corresponding [`SourceChange`]s | 3 | //! This is mostly front-end for [`ide_db::rename`], but it also includes the |
4 | //! will be calculated. | 4 | //! tests. This module also implements a couple of magic tricks, like renaming |
5 | use std::fmt::{self, Display}; | 5 | //! `self` and to `self` (to switch between associated function and method). |
6 | 6 | use hir::{AsAssocItem, InFile, Semantics}; | |
7 | use either::Either; | ||
8 | use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics}; | ||
9 | use ide_db::{ | 7 | use ide_db::{ |
10 | base_db::{AnchoredPathBuf, FileId, FileRange}, | 8 | base_db::FileId, |
11 | defs::{Definition, NameClass, NameRefClass}, | 9 | defs::{Definition, NameClass, NameRefClass}, |
12 | search::FileReference, | 10 | rename::{bail, format_err, source_edit_from_references, IdentifierKind}, |
13 | RootDatabase, | 11 | RootDatabase, |
14 | }; | 12 | }; |
15 | use stdx::never; | 13 | use stdx::never; |
16 | use syntax::{ | 14 | use syntax::{ast, AstNode, SyntaxNode}; |
17 | ast::{self, NameOwner}, | ||
18 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, | ||
19 | }; | ||
20 | 15 | ||
21 | use text_edit::TextEdit; | 16 | use text_edit::TextEdit; |
22 | 17 | ||
23 | use crate::{FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; | 18 | use crate::{FilePosition, RangeInfo, SourceChange}; |
24 | |||
25 | type RenameResult<T> = Result<T, RenameError>; | ||
26 | #[derive(Debug)] | ||
27 | pub struct RenameError(String); | ||
28 | |||
29 | impl fmt::Display for RenameError { | ||
30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
31 | Display::fmt(&self.0, f) | ||
32 | } | ||
33 | } | ||
34 | 19 | ||
35 | macro_rules! format_err { | 20 | pub use ide_db::rename::RenameError; |
36 | ($fmt:expr) => {RenameError(format!($fmt))}; | ||
37 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | ||
38 | } | ||
39 | 21 | ||
40 | macro_rules! bail { | 22 | type RenameResult<T> = Result<T, RenameError>; |
41 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | ||
42 | } | ||
43 | 23 | ||
44 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is | 24 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is |
45 | /// being targeted for a rename. | 25 | /// being targeted for a rename. |
@@ -52,7 +32,8 @@ pub(crate) fn prepare_rename( | |||
52 | let syntax = source_file.syntax(); | 32 | let syntax = source_file.syntax(); |
53 | 33 | ||
54 | let def = find_definition(&sema, syntax, position)?; | 34 | let def = find_definition(&sema, syntax, position)?; |
55 | let frange = def_name_range(&&sema, def) | 35 | let frange = def |
36 | .range_for_rename(&sema) | ||
56 | .ok_or_else(|| format_err!("No references found at position"))?; | 37 | .ok_or_else(|| format_err!("No references found at position"))?; |
57 | Ok(RangeInfo::new(frange.range, ())) | 38 | Ok(RangeInfo::new(frange.range, ())) |
58 | } | 39 | } |
@@ -98,14 +79,7 @@ pub(crate) fn rename_with_semantics( | |||
98 | } | 79 | } |
99 | } | 80 | } |
100 | 81 | ||
101 | match def { | 82 | def.rename(sema, new_name) |
102 | Definition::ModuleDef(hir::ModuleDef::Module(module)) => rename_mod(sema, module, new_name), | ||
103 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
104 | Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { | ||
105 | bail!("Cannot rename builtin type") | ||
106 | } | ||
107 | def => rename_reference(sema, def, new_name), | ||
108 | } | ||
109 | } | 83 | } |
110 | 84 | ||
111 | /// Called by the client when it is about to rename a file. | 85 | /// Called by the client when it is about to rename a file. |
@@ -116,38 +90,12 @@ pub(crate) fn will_rename_file( | |||
116 | ) -> Option<SourceChange> { | 90 | ) -> Option<SourceChange> { |
117 | let sema = Semantics::new(db); | 91 | let sema = Semantics::new(db); |
118 | let module = sema.to_module_def(file_id)?; | 92 | let module = sema.to_module_def(file_id)?; |
119 | let mut change = rename_mod(&sema, module, new_name_stem).ok()?; | 93 | let def = Definition::ModuleDef(module.into()); |
94 | let mut change = def.rename(&sema, new_name_stem).ok()?; | ||
120 | change.file_system_edits.clear(); | 95 | change.file_system_edits.clear(); |
121 | Some(change) | 96 | Some(change) |
122 | } | 97 | } |
123 | 98 | ||
124 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
125 | enum IdentifierKind { | ||
126 | Ident, | ||
127 | Lifetime, | ||
128 | Underscore, | ||
129 | } | ||
130 | |||
131 | impl IdentifierKind { | ||
132 | fn classify(new_name: &str) -> RenameResult<IdentifierKind> { | ||
133 | match lex_single_syntax_kind(new_name) { | ||
134 | Some(res) => match res { | ||
135 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
136 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
137 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
138 | Ok(IdentifierKind::Lifetime) | ||
139 | } | ||
140 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
141 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
142 | } | ||
143 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
144 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
145 | }, | ||
146 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | fn find_definition( | 99 | fn find_definition( |
152 | sema: &Semantics<RootDatabase>, | 100 | sema: &Semantics<RootDatabase>, |
153 | syntax: &SyntaxNode, | 101 | syntax: &SyntaxNode, |
@@ -189,126 +137,6 @@ fn find_definition( | |||
189 | .ok_or_else(|| format_err!("No references found at position")) | 137 | .ok_or_else(|| format_err!("No references found at position")) |
190 | } | 138 | } |
191 | 139 | ||
192 | fn rename_mod( | ||
193 | sema: &Semantics<RootDatabase>, | ||
194 | module: hir::Module, | ||
195 | new_name: &str, | ||
196 | ) -> RenameResult<SourceChange> { | ||
197 | if IdentifierKind::classify(new_name)? != IdentifierKind::Ident { | ||
198 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
199 | } | ||
200 | |||
201 | let mut source_change = SourceChange::default(); | ||
202 | |||
203 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); | ||
204 | let file_id = file_id.original_file(sema.db); | ||
205 | if let ModuleSource::SourceFile(..) = def_source { | ||
206 | // mod is defined in path/to/dir/mod.rs | ||
207 | let path = if module.is_mod_rs(sema.db) { | ||
208 | format!("../{}/mod.rs", new_name) | ||
209 | } else { | ||
210 | format!("{}.rs", new_name) | ||
211 | }; | ||
212 | let dst = AnchoredPathBuf { anchor: file_id, path }; | ||
213 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | ||
214 | source_change.push_file_system_edit(move_file); | ||
215 | } | ||
216 | |||
217 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { | ||
218 | let file_id = file_id.original_file(sema.db); | ||
219 | match decl_source.name() { | ||
220 | Some(name) => source_change.insert_source_edit( | ||
221 | file_id, | ||
222 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | ||
223 | ), | ||
224 | _ => never!("Module source node is missing a name"), | ||
225 | } | ||
226 | } | ||
227 | let def = Definition::ModuleDef(hir::ModuleDef::Module(module)); | ||
228 | let usages = def.usages(sema).all(); | ||
229 | let ref_edits = usages.iter().map(|(&file_id, references)| { | ||
230 | (file_id, source_edit_from_references(references, def, new_name)) | ||
231 | }); | ||
232 | source_change.extend(ref_edits); | ||
233 | |||
234 | Ok(source_change) | ||
235 | } | ||
236 | |||
237 | fn rename_reference( | ||
238 | sema: &Semantics<RootDatabase>, | ||
239 | mut def: Definition, | ||
240 | new_name: &str, | ||
241 | ) -> RenameResult<SourceChange> { | ||
242 | let ident_kind = IdentifierKind::classify(new_name)?; | ||
243 | |||
244 | if matches!( | ||
245 | def, // is target a lifetime? | ||
246 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
247 | ) { | ||
248 | match ident_kind { | ||
249 | IdentifierKind::Ident | IdentifierKind::Underscore => { | ||
250 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
251 | bail!("Invalid name `{}`: not a lifetime identifier", new_name); | ||
252 | } | ||
253 | IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), | ||
254 | } | ||
255 | } else { | ||
256 | match (ident_kind, def) { | ||
257 | (IdentifierKind::Lifetime, _) => { | ||
258 | cov_mark::hit!(rename_not_an_ident_ref); | ||
259 | bail!("Invalid name `{}`: not an identifier", new_name); | ||
260 | } | ||
261 | (IdentifierKind::Ident, _) => cov_mark::hit!(rename_non_local), | ||
262 | (IdentifierKind::Underscore, _) => (), | ||
263 | } | ||
264 | } | ||
265 | |||
266 | def = match def { | ||
267 | // HACK: resolve trait impl items to the item def of the trait definition | ||
268 | // so that we properly resolve all trait item references | ||
269 | Definition::ModuleDef(mod_def) => mod_def | ||
270 | .as_assoc_item(sema.db) | ||
271 | .and_then(|it| it.containing_trait_impl(sema.db)) | ||
272 | .and_then(|it| { | ||
273 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { | ||
274 | (hir::AssocItem::Function(trait_func), hir::ModuleDef::Function(func)) | ||
275 | if trait_func.name(sema.db) == func.name(sema.db) => | ||
276 | { | ||
277 | Some(Definition::ModuleDef(hir::ModuleDef::Function(trait_func))) | ||
278 | } | ||
279 | (hir::AssocItem::Const(trait_konst), hir::ModuleDef::Const(konst)) | ||
280 | if trait_konst.name(sema.db) == konst.name(sema.db) => | ||
281 | { | ||
282 | Some(Definition::ModuleDef(hir::ModuleDef::Const(trait_konst))) | ||
283 | } | ||
284 | ( | ||
285 | hir::AssocItem::TypeAlias(trait_type_alias), | ||
286 | hir::ModuleDef::TypeAlias(type_alias), | ||
287 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { | ||
288 | Some(Definition::ModuleDef(hir::ModuleDef::TypeAlias(trait_type_alias))) | ||
289 | } | ||
290 | _ => None, | ||
291 | }) | ||
292 | }) | ||
293 | .unwrap_or(def), | ||
294 | _ => def, | ||
295 | }; | ||
296 | let usages = def.usages(sema).all(); | ||
297 | |||
298 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
299 | cov_mark::hit!(rename_underscore_multiple); | ||
300 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
301 | } | ||
302 | let mut source_change = SourceChange::default(); | ||
303 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
304 | (file_id, source_edit_from_references(references, def, new_name)) | ||
305 | })); | ||
306 | |||
307 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
308 | source_change.insert_source_edit(file_id, edit); | ||
309 | Ok(source_change) | ||
310 | } | ||
311 | |||
312 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { | 140 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
313 | if never!(local.is_self(sema.db)) { | 141 | if never!(local.is_self(sema.db)) { |
314 | bail!("rename_to_self invoked on self"); | 142 | bail!("rename_to_self invoked on self"); |
@@ -426,243 +254,6 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt | |||
426 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | 254 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) |
427 | } | 255 | } |
428 | 256 | ||
429 | fn source_edit_from_references( | ||
430 | references: &[FileReference], | ||
431 | def: Definition, | ||
432 | new_name: &str, | ||
433 | ) -> TextEdit { | ||
434 | let mut edit = TextEdit::builder(); | ||
435 | for reference in references { | ||
436 | let (range, replacement) = match &reference.name { | ||
437 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
438 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
439 | // the macro input | ||
440 | ast::NameLike::NameRef(name_ref) | ||
441 | if name_ref.syntax().text_range() == reference.range => | ||
442 | { | ||
443 | source_edit_from_name_ref(name_ref, new_name, def) | ||
444 | } | ||
445 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
446 | source_edit_from_name(name, new_name) | ||
447 | } | ||
448 | _ => None, | ||
449 | } | ||
450 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
451 | edit.replace(range, replacement); | ||
452 | } | ||
453 | edit.finish() | ||
454 | } | ||
455 | |||
456 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
457 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
458 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
459 | return Some(( | ||
460 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
461 | [new_name, ": "].concat(), | ||
462 | )); | ||
463 | } | ||
464 | } | ||
465 | None | ||
466 | } | ||
467 | |||
468 | fn source_edit_from_name_ref( | ||
469 | name_ref: &ast::NameRef, | ||
470 | new_name: &str, | ||
471 | def: Definition, | ||
472 | ) -> Option<(TextRange, String)> { | ||
473 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
474 | let rcf_name_ref = record_field.name_ref(); | ||
475 | let rcf_expr = record_field.expr(); | ||
476 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
477 | // field: init-expr, check if we can use a field init shorthand | ||
478 | (Some(field_name), Some(init)) => { | ||
479 | if field_name == *name_ref { | ||
480 | if init.text() == new_name { | ||
481 | cov_mark::hit!(test_rename_field_put_init_shorthand); | ||
482 | // same names, we can use a shorthand here instead. | ||
483 | // we do not want to erase attributes hence this range start | ||
484 | let s = field_name.syntax().text_range().start(); | ||
485 | let e = record_field.syntax().text_range().end(); | ||
486 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
487 | } | ||
488 | } else if init == *name_ref { | ||
489 | if field_name.text() == new_name { | ||
490 | cov_mark::hit!(test_rename_local_put_init_shorthand); | ||
491 | // same names, we can use a shorthand here instead. | ||
492 | // we do not want to erase attributes hence this range start | ||
493 | let s = field_name.syntax().text_range().start(); | ||
494 | let e = record_field.syntax().text_range().end(); | ||
495 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
496 | } | ||
497 | } | ||
498 | None | ||
499 | } | ||
500 | // init shorthand | ||
501 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
502 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
503 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
504 | cov_mark::hit!(test_rename_field_in_field_shorthand); | ||
505 | let s = name_ref.syntax().text_range().start(); | ||
506 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
507 | } | ||
508 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
509 | cov_mark::hit!(test_rename_local_in_field_shorthand); | ||
510 | let s = name_ref.syntax().text_range().end(); | ||
511 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
512 | } | ||
513 | _ => None, | ||
514 | } | ||
515 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
516 | let rcf_name_ref = record_field.name_ref(); | ||
517 | let rcf_pat = record_field.pat(); | ||
518 | match (rcf_name_ref, rcf_pat) { | ||
519 | // field: rename | ||
520 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
521 | // field name is being renamed | ||
522 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
523 | cov_mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
524 | // same names, we can use a shorthand here instead/ | ||
525 | // we do not want to erase attributes hence this range start | ||
526 | let s = field_name.syntax().text_range().start(); | ||
527 | let e = record_field.syntax().text_range().end(); | ||
528 | Some((TextRange::new(s, e), pat.to_string())) | ||
529 | } else { | ||
530 | None | ||
531 | } | ||
532 | } | ||
533 | _ => None, | ||
534 | } | ||
535 | } else { | ||
536 | None | ||
537 | } | ||
538 | } | ||
539 | |||
540 | fn source_edit_from_def( | ||
541 | sema: &Semantics<RootDatabase>, | ||
542 | def: Definition, | ||
543 | new_name: &str, | ||
544 | ) -> RenameResult<(FileId, TextEdit)> { | ||
545 | let frange: FileRange = def_name_range(sema, def) | ||
546 | .ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
547 | |||
548 | let mut replacement_text = String::new(); | ||
549 | let mut repl_range = frange.range; | ||
550 | if let Definition::Local(local) = def { | ||
551 | if let Either::Left(pat) = local.source(sema.db).value { | ||
552 | if matches!( | ||
553 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
554 | Some(pat_field) if pat_field.name_ref().is_none() | ||
555 | ) { | ||
556 | replacement_text.push_str(": "); | ||
557 | replacement_text.push_str(new_name); | ||
558 | repl_range = TextRange::new( | ||
559 | pat.syntax().text_range().end(), | ||
560 | pat.syntax().text_range().end(), | ||
561 | ); | ||
562 | } | ||
563 | } | ||
564 | } | ||
565 | if replacement_text.is_empty() { | ||
566 | replacement_text.push_str(new_name); | ||
567 | } | ||
568 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
569 | Ok((frange.file_id, edit)) | ||
570 | } | ||
571 | |||
572 | fn def_name_range(sema: &Semantics<RootDatabase>, def: Definition) -> Option<FileRange> { | ||
573 | // FIXME: the `original_file_range` calls here are wrong -- they never fail, | ||
574 | // and _fall back_ to the entirety of the macro call. Such fall back is | ||
575 | // incorrect for renames. The safe behavior would be to return an error for | ||
576 | // such cases. The correct behavior would be to return an auxiliary list of | ||
577 | // "can't rename these occurrences in macros" items, and then show some kind | ||
578 | // of a dialog to the user. | ||
579 | |||
580 | let res = match def { | ||
581 | Definition::Macro(mac) => { | ||
582 | let src = mac.source(sema.db)?; | ||
583 | let name = match &src.value { | ||
584 | Either::Left(it) => it.name()?, | ||
585 | Either::Right(it) => it.name()?, | ||
586 | }; | ||
587 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
588 | } | ||
589 | Definition::Field(field) => { | ||
590 | let src = field.source(sema.db)?; | ||
591 | |||
592 | match &src.value { | ||
593 | FieldSource::Named(record_field) => { | ||
594 | let name = record_field.name()?; | ||
595 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
596 | } | ||
597 | FieldSource::Pos(_) => { | ||
598 | return None; | ||
599 | } | ||
600 | } | ||
601 | } | ||
602 | Definition::ModuleDef(module_def) => match module_def { | ||
603 | hir::ModuleDef::Module(module) => { | ||
604 | let src = module.declaration_source(sema.db)?; | ||
605 | let name = src.value.name()?; | ||
606 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
607 | } | ||
608 | hir::ModuleDef::Function(it) => name_range(it, sema)?, | ||
609 | hir::ModuleDef::Adt(adt) => match adt { | ||
610 | hir::Adt::Struct(it) => name_range(it, sema)?, | ||
611 | hir::Adt::Union(it) => name_range(it, sema)?, | ||
612 | hir::Adt::Enum(it) => name_range(it, sema)?, | ||
613 | }, | ||
614 | hir::ModuleDef::Variant(it) => name_range(it, sema)?, | ||
615 | hir::ModuleDef::Const(it) => name_range(it, sema)?, | ||
616 | hir::ModuleDef::Static(it) => name_range(it, sema)?, | ||
617 | hir::ModuleDef::Trait(it) => name_range(it, sema)?, | ||
618 | hir::ModuleDef::TypeAlias(it) => name_range(it, sema)?, | ||
619 | hir::ModuleDef::BuiltinType(_) => return None, | ||
620 | }, | ||
621 | Definition::SelfType(_) => return None, | ||
622 | Definition::Local(local) => { | ||
623 | let src = local.source(sema.db); | ||
624 | let name = match &src.value { | ||
625 | Either::Left(bind_pat) => bind_pat.name()?, | ||
626 | Either::Right(_) => return None, | ||
627 | }; | ||
628 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
629 | } | ||
630 | Definition::GenericParam(generic_param) => match generic_param { | ||
631 | hir::GenericParam::TypeParam(type_param) => { | ||
632 | let src = type_param.source(sema.db)?; | ||
633 | let name = match &src.value { | ||
634 | Either::Left(_) => return None, | ||
635 | Either::Right(type_param) => type_param.name()?, | ||
636 | }; | ||
637 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
638 | } | ||
639 | hir::GenericParam::LifetimeParam(lifetime_param) => { | ||
640 | let src = lifetime_param.source(sema.db)?; | ||
641 | let lifetime = src.value.lifetime()?; | ||
642 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
643 | } | ||
644 | hir::GenericParam::ConstParam(it) => name_range(it, sema)?, | ||
645 | }, | ||
646 | Definition::Label(label) => { | ||
647 | let src = label.source(sema.db); | ||
648 | let lifetime = src.value.lifetime()?; | ||
649 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
650 | } | ||
651 | }; | ||
652 | return Some(res); | ||
653 | |||
654 | fn name_range<D>(def: D, sema: &Semantics<RootDatabase>) -> Option<FileRange> | ||
655 | where | ||
656 | D: HasSource, | ||
657 | D::Ast: ast::NameOwner, | ||
658 | { | ||
659 | let src = def.source(sema.db)?; | ||
660 | let name = src.value.name()?; | ||
661 | let res = src.with_value(name.syntax()).original_file_range(sema.db); | ||
662 | Some(res) | ||
663 | } | ||
664 | } | ||
665 | |||
666 | #[cfg(test)] | 257 | #[cfg(test)] |
667 | mod tests { | 258 | mod tests { |
668 | use expect_test::{expect, Expect}; | 259 | use expect_test::{expect, Expect}; |
@@ -2178,4 +1769,22 @@ fn f() { <()>::BAR$0; }"#, | |||
2178 | res, | 1769 | res, |
2179 | ); | 1770 | ); |
2180 | } | 1771 | } |
1772 | |||
1773 | #[test] | ||
1774 | fn macros_are_broken_lol() { | ||
1775 | cov_mark::check!(macros_are_broken_lol); | ||
1776 | check( | ||
1777 | "lol", | ||
1778 | r#" | ||
1779 | macro_rules! m { () => { fn f() {} } } | ||
1780 | m!(); | ||
1781 | fn main() { f$0() } | ||
1782 | "#, | ||
1783 | r#" | ||
1784 | macro_rules! m { () => { fn f() {} } } | ||
1785 | lol | ||
1786 | fn main() { lol() } | ||
1787 | "#, | ||
1788 | ) | ||
1789 | } | ||
2181 | } | 1790 | } |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 331a6df2b..fa378a622 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -17,156 +17,31 @@ mod tests; | |||
17 | pub mod utils; | 17 | pub mod utils; |
18 | pub mod path_transform; | 18 | pub mod path_transform; |
19 | 19 | ||
20 | use std::str::FromStr; | ||
21 | |||
22 | use hir::Semantics; | 20 | use hir::Semantics; |
23 | use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; | 21 | use ide_db::{base_db::FileRange, RootDatabase}; |
24 | use syntax::TextRange; | 22 | use syntax::TextRange; |
25 | 23 | ||
26 | pub(crate) use crate::assist_context::{AssistContext, Assists}; | 24 | pub(crate) use crate::assist_context::{AssistContext, Assists}; |
27 | 25 | ||
28 | pub use assist_config::AssistConfig; | 26 | pub use assist_config::AssistConfig; |
29 | 27 | pub use ide_db::assists::{ | |
30 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 28 | Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, SingleResolve, |
31 | pub enum AssistKind { | 29 | }; |
32 | // FIXME: does the None variant make sense? Probably not. | 30 | |
33 | None, | 31 | /// Return all the assists applicable at the given position. |
34 | 32 | pub fn assists( | |
35 | QuickFix, | 33 | db: &RootDatabase, |
36 | Generate, | 34 | config: &AssistConfig, |
37 | Refactor, | 35 | resolve: AssistResolveStrategy, |
38 | RefactorExtract, | 36 | range: FileRange, |
39 | RefactorInline, | 37 | ) -> Vec<Assist> { |
40 | RefactorRewrite, | 38 | let sema = Semantics::new(db); |
41 | } | 39 | let ctx = AssistContext::new(sema, config, range); |
42 | 40 | let mut acc = Assists::new(&ctx, resolve); | |
43 | impl AssistKind { | 41 | handlers::all().iter().for_each(|handler| { |
44 | pub fn contains(self, other: AssistKind) -> bool { | 42 | handler(&mut acc, &ctx); |
45 | if self == other { | 43 | }); |
46 | return true; | 44 | acc.finish() |
47 | } | ||
48 | |||
49 | match self { | ||
50 | AssistKind::None | AssistKind::Generate => true, | ||
51 | AssistKind::Refactor => match other { | ||
52 | AssistKind::RefactorExtract | ||
53 | | AssistKind::RefactorInline | ||
54 | | AssistKind::RefactorRewrite => true, | ||
55 | _ => false, | ||
56 | }, | ||
57 | _ => false, | ||
58 | } | ||
59 | } | ||
60 | |||
61 | pub fn name(&self) -> &str { | ||
62 | match self { | ||
63 | AssistKind::None => "None", | ||
64 | AssistKind::QuickFix => "QuickFix", | ||
65 | AssistKind::Generate => "Generate", | ||
66 | AssistKind::Refactor => "Refactor", | ||
67 | AssistKind::RefactorExtract => "RefactorExtract", | ||
68 | AssistKind::RefactorInline => "RefactorInline", | ||
69 | AssistKind::RefactorRewrite => "RefactorRewrite", | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | |||
74 | impl FromStr for AssistKind { | ||
75 | type Err = String; | ||
76 | |||
77 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
78 | match s { | ||
79 | "None" => Ok(AssistKind::None), | ||
80 | "QuickFix" => Ok(AssistKind::QuickFix), | ||
81 | "Generate" => Ok(AssistKind::Generate), | ||
82 | "Refactor" => Ok(AssistKind::Refactor), | ||
83 | "RefactorExtract" => Ok(AssistKind::RefactorExtract), | ||
84 | "RefactorInline" => Ok(AssistKind::RefactorInline), | ||
85 | "RefactorRewrite" => Ok(AssistKind::RefactorRewrite), | ||
86 | unknown => Err(format!("Unknown AssistKind: '{}'", unknown)), | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | /// Unique identifier of the assist, should not be shown to the user | ||
92 | /// directly. | ||
93 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
94 | pub struct AssistId(pub &'static str, pub AssistKind); | ||
95 | |||
96 | /// A way to control how many asssist to resolve during the assist resolution. | ||
97 | /// When an assist is resolved, its edits are calculated that might be costly to always do by default. | ||
98 | #[derive(Debug)] | ||
99 | pub enum AssistResolveStrategy { | ||
100 | /// No assists should be resolved. | ||
101 | None, | ||
102 | /// All assists should be resolved. | ||
103 | All, | ||
104 | /// Only a certain assist should be resolved. | ||
105 | Single(SingleResolve), | ||
106 | } | ||
107 | |||
108 | /// Hold the [`AssistId`] data of a certain assist to resolve. | ||
109 | /// The original id object cannot be used due to a `'static` lifetime | ||
110 | /// and the requirement to construct this struct dynamically during the resolve handling. | ||
111 | #[derive(Debug)] | ||
112 | pub struct SingleResolve { | ||
113 | /// The id of the assist. | ||
114 | pub assist_id: String, | ||
115 | // The kind of the assist. | ||
116 | pub assist_kind: AssistKind, | ||
117 | } | ||
118 | |||
119 | impl AssistResolveStrategy { | ||
120 | pub fn should_resolve(&self, id: &AssistId) -> bool { | ||
121 | match self { | ||
122 | AssistResolveStrategy::None => false, | ||
123 | AssistResolveStrategy::All => true, | ||
124 | AssistResolveStrategy::Single(single_resolve) => { | ||
125 | single_resolve.assist_id == id.0 && single_resolve.assist_kind == id.1 | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | #[derive(Clone, Debug)] | ||
132 | pub struct GroupLabel(pub String); | ||
133 | |||
134 | #[derive(Debug, Clone)] | ||
135 | pub struct Assist { | ||
136 | pub id: AssistId, | ||
137 | /// Short description of the assist, as shown in the UI. | ||
138 | pub label: Label, | ||
139 | pub group: Option<GroupLabel>, | ||
140 | /// Target ranges are used to sort assists: the smaller the target range, | ||
141 | /// the more specific assist is, and so it should be sorted first. | ||
142 | pub target: TextRange, | ||
143 | /// Computing source change sometimes is much more costly then computing the | ||
144 | /// other fields. Additionally, the actual change is not required to show | ||
145 | /// the lightbulb UI, it only is needed when the user tries to apply an | ||
146 | /// assist. So, we compute it lazily: the API allow requesting assists with | ||
147 | /// or without source change. We could (and in fact, used to) distinguish | ||
148 | /// between resolved and unresolved assists at the type level, but this is | ||
149 | /// cumbersome, especially if you want to embed an assist into another data | ||
150 | /// structure, such as a diagnostic. | ||
151 | pub source_change: Option<SourceChange>, | ||
152 | } | ||
153 | |||
154 | impl Assist { | ||
155 | /// Return all the assists applicable at the given position. | ||
156 | pub fn get( | ||
157 | db: &RootDatabase, | ||
158 | config: &AssistConfig, | ||
159 | resolve: AssistResolveStrategy, | ||
160 | range: FileRange, | ||
161 | ) -> Vec<Assist> { | ||
162 | let sema = Semantics::new(db); | ||
163 | let ctx = AssistContext::new(sema, config, range); | ||
164 | let mut acc = Assists::new(&ctx, resolve); | ||
165 | handlers::all().iter().for_each(|handler| { | ||
166 | handler(&mut acc, &ctx); | ||
167 | }); | ||
168 | acc.finish() | ||
169 | } | ||
170 | } | 45 | } |
171 | 46 | ||
172 | mod handlers { | 47 | mod handlers { |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index bdf9cb71c..60cecd94c 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -16,8 +16,8 @@ use syntax::TextRange; | |||
16 | use test_utils::{assert_eq_text, extract_offset}; | 16 | use test_utils::{assert_eq_text, extract_offset}; |
17 | 17 | ||
18 | use crate::{ | 18 | use crate::{ |
19 | handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, AssistResolveStrategy, | 19 | assists, handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, |
20 | Assists, SingleResolve, | 20 | AssistResolveStrategy, Assists, SingleResolve, |
21 | }; | 21 | }; |
22 | 22 | ||
23 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { | 23 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { |
@@ -78,14 +78,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
78 | let before = db.file_text(file_id).to_string(); | 78 | let before = db.file_text(file_id).to_string(); |
79 | let frange = FileRange { file_id, range: selection.into() }; | 79 | let frange = FileRange { file_id, range: selection.into() }; |
80 | 80 | ||
81 | let assist = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange) | 81 | let assist = assists(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange) |
82 | .into_iter() | 82 | .into_iter() |
83 | .find(|assist| assist.id.0 == assist_id) | 83 | .find(|assist| assist.id.0 == assist_id) |
84 | .unwrap_or_else(|| { | 84 | .unwrap_or_else(|| { |
85 | panic!( | 85 | panic!( |
86 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 86 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
87 | assist_id, | 87 | assist_id, |
88 | Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange) | 88 | assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange) |
89 | .into_iter() | 89 | .into_iter() |
90 | .map(|assist| assist.id.0) | 90 | .map(|assist| assist.id.0) |
91 | .collect::<Vec<_>>() | 91 | .collect::<Vec<_>>() |
@@ -210,7 +210,7 @@ fn assist_order_field_struct() { | |||
210 | let (before_cursor_pos, before) = extract_offset(before); | 210 | let (before_cursor_pos, before) = extract_offset(before); |
211 | let (db, file_id) = with_single_file(&before); | 211 | let (db, file_id) = with_single_file(&before); |
212 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 212 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
213 | let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); | 213 | let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); |
214 | let mut assists = assists.iter(); | 214 | let mut assists = assists.iter(); |
215 | 215 | ||
216 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 216 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
@@ -235,7 +235,7 @@ pub fn test_some_range(a: int) -> bool { | |||
235 | "#, | 235 | "#, |
236 | ); | 236 | ); |
237 | 237 | ||
238 | let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); | 238 | let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); |
239 | let expected = labels(&assists); | 239 | let expected = labels(&assists); |
240 | 240 | ||
241 | expect![[r#" | 241 | expect![[r#" |
@@ -264,7 +264,7 @@ pub fn test_some_range(a: int) -> bool { | |||
264 | let mut cfg = TEST_CONFIG; | 264 | let mut cfg = TEST_CONFIG; |
265 | cfg.allowed = Some(vec![AssistKind::Refactor]); | 265 | cfg.allowed = Some(vec![AssistKind::Refactor]); |
266 | 266 | ||
267 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); | 267 | let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange); |
268 | let expected = labels(&assists); | 268 | let expected = labels(&assists); |
269 | 269 | ||
270 | expect![[r#" | 270 | expect![[r#" |
@@ -279,7 +279,7 @@ pub fn test_some_range(a: int) -> bool { | |||
279 | { | 279 | { |
280 | let mut cfg = TEST_CONFIG; | 280 | let mut cfg = TEST_CONFIG; |
281 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | 281 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); |
282 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); | 282 | let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange); |
283 | let expected = labels(&assists); | 283 | let expected = labels(&assists); |
284 | 284 | ||
285 | expect![[r#" | 285 | expect![[r#" |
@@ -292,7 +292,7 @@ pub fn test_some_range(a: int) -> bool { | |||
292 | { | 292 | { |
293 | let mut cfg = TEST_CONFIG; | 293 | let mut cfg = TEST_CONFIG; |
294 | cfg.allowed = Some(vec![AssistKind::QuickFix]); | 294 | cfg.allowed = Some(vec![AssistKind::QuickFix]); |
295 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); | 295 | let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange); |
296 | let expected = labels(&assists); | 296 | let expected = labels(&assists); |
297 | 297 | ||
298 | expect![[r#""#]].assert_eq(&expected); | 298 | expect![[r#""#]].assert_eq(&expected); |
@@ -317,7 +317,7 @@ pub fn test_some_range(a: int) -> bool { | |||
317 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | 317 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); |
318 | 318 | ||
319 | { | 319 | { |
320 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); | 320 | let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange); |
321 | assert_eq!(2, assists.len()); | 321 | assert_eq!(2, assists.len()); |
322 | let mut assists = assists.into_iter(); | 322 | let mut assists = assists.into_iter(); |
323 | 323 | ||
@@ -353,7 +353,7 @@ pub fn test_some_range(a: int) -> bool { | |||
353 | } | 353 | } |
354 | 354 | ||
355 | { | 355 | { |
356 | let assists = Assist::get( | 356 | let assists = assists( |
357 | &db, | 357 | &db, |
358 | &cfg, | 358 | &cfg, |
359 | AssistResolveStrategy::Single(SingleResolve { | 359 | AssistResolveStrategy::Single(SingleResolve { |
@@ -397,7 +397,7 @@ pub fn test_some_range(a: int) -> bool { | |||
397 | } | 397 | } |
398 | 398 | ||
399 | { | 399 | { |
400 | let assists = Assist::get( | 400 | let assists = assists( |
401 | &db, | 401 | &db, |
402 | &cfg, | 402 | &cfg, |
403 | AssistResolveStrategy::Single(SingleResolve { | 403 | AssistResolveStrategy::Single(SingleResolve { |
@@ -462,7 +462,7 @@ pub fn test_some_range(a: int) -> bool { | |||
462 | } | 462 | } |
463 | 463 | ||
464 | { | 464 | { |
465 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::All, frange); | 465 | let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange); |
466 | assert_eq!(2, assists.len()); | 466 | assert_eq!(2, assists.len()); |
467 | let mut assists = assists.into_iter(); | 467 | let mut assists = assists.into_iter(); |
468 | 468 | ||
diff --git a/crates/ide_db/src/assists.rs b/crates/ide_db/src/assists.rs new file mode 100644 index 000000000..7881d8369 --- /dev/null +++ b/crates/ide_db/src/assists.rs | |||
@@ -0,0 +1,136 @@ | |||
1 | //! This module defines the `Assist` data structure. The actual assist live in | ||
2 | //! the `ide_assists` downstream crate. We want to define the data structures in | ||
3 | //! this low-level crate though, because `ide_diagnostics` also need them | ||
4 | //! (fixits for diagnostics and assists are the same thing under the hood). We | ||
5 | //! want to compile `ide_assists` and `ide_diagnostics` in parallel though, so | ||
6 | //! we pull the common definitions upstream, to this crate. | ||
7 | |||
8 | use std::str::FromStr; | ||
9 | |||
10 | use syntax::TextRange; | ||
11 | |||
12 | use crate::{label::Label, source_change::SourceChange}; | ||
13 | |||
14 | #[derive(Debug, Clone)] | ||
15 | pub struct Assist { | ||
16 | pub id: AssistId, | ||
17 | /// Short description of the assist, as shown in the UI. | ||
18 | pub label: Label, | ||
19 | pub group: Option<GroupLabel>, | ||
20 | /// Target ranges are used to sort assists: the smaller the target range, | ||
21 | /// the more specific assist is, and so it should be sorted first. | ||
22 | pub target: TextRange, | ||
23 | /// Computing source change sometimes is much more costly then computing the | ||
24 | /// other fields. Additionally, the actual change is not required to show | ||
25 | /// the lightbulb UI, it only is needed when the user tries to apply an | ||
26 | /// assist. So, we compute it lazily: the API allow requesting assists with | ||
27 | /// or without source change. We could (and in fact, used to) distinguish | ||
28 | /// between resolved and unresolved assists at the type level, but this is | ||
29 | /// cumbersome, especially if you want to embed an assist into another data | ||
30 | /// structure, such as a diagnostic. | ||
31 | pub source_change: Option<SourceChange>, | ||
32 | } | ||
33 | |||
34 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
35 | pub enum AssistKind { | ||
36 | // FIXME: does the None variant make sense? Probably not. | ||
37 | None, | ||
38 | |||
39 | QuickFix, | ||
40 | Generate, | ||
41 | Refactor, | ||
42 | RefactorExtract, | ||
43 | RefactorInline, | ||
44 | RefactorRewrite, | ||
45 | } | ||
46 | |||
47 | impl AssistKind { | ||
48 | pub fn contains(self, other: AssistKind) -> bool { | ||
49 | if self == other { | ||
50 | return true; | ||
51 | } | ||
52 | |||
53 | match self { | ||
54 | AssistKind::None | AssistKind::Generate => true, | ||
55 | AssistKind::Refactor => match other { | ||
56 | AssistKind::RefactorExtract | ||
57 | | AssistKind::RefactorInline | ||
58 | | AssistKind::RefactorRewrite => true, | ||
59 | _ => false, | ||
60 | }, | ||
61 | _ => false, | ||
62 | } | ||
63 | } | ||
64 | |||
65 | pub fn name(&self) -> &str { | ||
66 | match self { | ||
67 | AssistKind::None => "None", | ||
68 | AssistKind::QuickFix => "QuickFix", | ||
69 | AssistKind::Generate => "Generate", | ||
70 | AssistKind::Refactor => "Refactor", | ||
71 | AssistKind::RefactorExtract => "RefactorExtract", | ||
72 | AssistKind::RefactorInline => "RefactorInline", | ||
73 | AssistKind::RefactorRewrite => "RefactorRewrite", | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | impl FromStr for AssistKind { | ||
79 | type Err = String; | ||
80 | |||
81 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
82 | match s { | ||
83 | "None" => Ok(AssistKind::None), | ||
84 | "QuickFix" => Ok(AssistKind::QuickFix), | ||
85 | "Generate" => Ok(AssistKind::Generate), | ||
86 | "Refactor" => Ok(AssistKind::Refactor), | ||
87 | "RefactorExtract" => Ok(AssistKind::RefactorExtract), | ||
88 | "RefactorInline" => Ok(AssistKind::RefactorInline), | ||
89 | "RefactorRewrite" => Ok(AssistKind::RefactorRewrite), | ||
90 | unknown => Err(format!("Unknown AssistKind: '{}'", unknown)), | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | |||
95 | /// Unique identifier of the assist, should not be shown to the user | ||
96 | /// directly. | ||
97 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
98 | pub struct AssistId(pub &'static str, pub AssistKind); | ||
99 | |||
100 | /// A way to control how many asssist to resolve during the assist resolution. | ||
101 | /// When an assist is resolved, its edits are calculated that might be costly to always do by default. | ||
102 | #[derive(Debug)] | ||
103 | pub enum AssistResolveStrategy { | ||
104 | /// No assists should be resolved. | ||
105 | None, | ||
106 | /// All assists should be resolved. | ||
107 | All, | ||
108 | /// Only a certain assist should be resolved. | ||
109 | Single(SingleResolve), | ||
110 | } | ||
111 | |||
112 | /// Hold the [`AssistId`] data of a certain assist to resolve. | ||
113 | /// The original id object cannot be used due to a `'static` lifetime | ||
114 | /// and the requirement to construct this struct dynamically during the resolve handling. | ||
115 | #[derive(Debug)] | ||
116 | pub struct SingleResolve { | ||
117 | /// The id of the assist. | ||
118 | pub assist_id: String, | ||
119 | // The kind of the assist. | ||
120 | pub assist_kind: AssistKind, | ||
121 | } | ||
122 | |||
123 | impl AssistResolveStrategy { | ||
124 | pub fn should_resolve(&self, id: &AssistId) -> bool { | ||
125 | match self { | ||
126 | AssistResolveStrategy::None => false, | ||
127 | AssistResolveStrategy::All => true, | ||
128 | AssistResolveStrategy::Single(single_resolve) => { | ||
129 | single_resolve.assist_id == id.0 && single_resolve.assist_kind == id.1 | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | #[derive(Clone, Debug)] | ||
136 | pub struct GroupLabel(pub String); | ||
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 105607dca..7bbd08d6f 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs | |||
@@ -3,11 +3,11 @@ | |||
3 | //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. | 3 | //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. |
4 | 4 | ||
5 | mod apply_change; | 5 | mod apply_change; |
6 | pub mod assists; | ||
6 | pub mod label; | 7 | pub mod label; |
7 | pub mod line_index; | 8 | pub mod line_index; |
8 | pub mod symbol_index; | 9 | pub mod symbol_index; |
9 | pub mod defs; | 10 | pub mod defs; |
10 | pub mod search; | ||
11 | pub mod items_locator; | 11 | pub mod items_locator; |
12 | pub mod source_change; | 12 | pub mod source_change; |
13 | pub mod ty_filter; | 13 | pub mod ty_filter; |
@@ -15,6 +15,9 @@ pub mod traits; | |||
15 | pub mod call_info; | 15 | pub mod call_info; |
16 | pub mod helpers; | 16 | pub mod helpers; |
17 | 17 | ||
18 | pub mod search; | ||
19 | pub mod rename; | ||
20 | |||
18 | use std::{fmt, sync::Arc}; | 21 | use std::{fmt, sync::Arc}; |
19 | 22 | ||
20 | use base_db::{ | 23 | use base_db::{ |
diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs new file mode 100644 index 000000000..82855725f --- /dev/null +++ b/crates/ide_db/src/rename.rs | |||
@@ -0,0 +1,468 @@ | |||
1 | //! Rename infrastructure for rust-analyzer. It is used primarily for the | ||
2 | //! literal "rename" in the ide (look for tests there), but it is also available | ||
3 | //! as a general-purpose service. For example, it is used by the fix for the | ||
4 | //! "incorrect case" diagnostic. | ||
5 | //! | ||
6 | //! It leverages the [`crate::search`] functionality to find what needs to be | ||
7 | //! renamed. The actual renames are tricky -- field shorthands need special | ||
8 | //! attention, and, when renaming modules, you also want to rename files on the | ||
9 | //! file system. | ||
10 | //! | ||
11 | //! Another can of worms are macros: | ||
12 | //! | ||
13 | //! ``` | ||
14 | //! macro_rules! m { () => { fn f() {} } } | ||
15 | //! m!(); | ||
16 | //! fn main() { | ||
17 | //! f() // <- rename me | ||
18 | //! } | ||
19 | //! ``` | ||
20 | //! | ||
21 | //! The correct behavior in such cases is probably to show a dialog to the user. | ||
22 | //! Our current behavior is ¯\_(ツ)_/¯. | ||
23 | use std::fmt; | ||
24 | |||
25 | use base_db::{AnchoredPathBuf, FileId, FileRange}; | ||
26 | use either::Either; | ||
27 | use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics}; | ||
28 | use stdx::never; | ||
29 | use syntax::{ | ||
30 | ast::{self, NameOwner}, | ||
31 | lex_single_syntax_kind, AstNode, SyntaxKind, TextRange, T, | ||
32 | }; | ||
33 | use text_edit::TextEdit; | ||
34 | |||
35 | use crate::{ | ||
36 | defs::Definition, | ||
37 | search::FileReference, | ||
38 | source_change::{FileSystemEdit, SourceChange}, | ||
39 | RootDatabase, | ||
40 | }; | ||
41 | |||
42 | pub type Result<T, E = RenameError> = std::result::Result<T, E>; | ||
43 | |||
44 | #[derive(Debug)] | ||
45 | pub struct RenameError(pub String); | ||
46 | |||
47 | impl fmt::Display for RenameError { | ||
48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
49 | fmt::Display::fmt(&self.0, f) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[macro_export] | ||
54 | macro_rules! _format_err { | ||
55 | ($fmt:expr) => { RenameError(format!($fmt)) }; | ||
56 | ($fmt:expr, $($arg:tt)+) => { RenameError(format!($fmt, $($arg)+)) } | ||
57 | } | ||
58 | pub use _format_err as format_err; | ||
59 | |||
60 | #[macro_export] | ||
61 | macro_rules! _bail { | ||
62 | ($($tokens:tt)*) => { return Err(format_err!($($tokens)*)) } | ||
63 | } | ||
64 | pub use _bail as bail; | ||
65 | |||
66 | impl Definition { | ||
67 | pub fn rename(&self, sema: &Semantics<RootDatabase>, new_name: &str) -> Result<SourceChange> { | ||
68 | match *self { | ||
69 | Definition::ModuleDef(hir::ModuleDef::Module(module)) => { | ||
70 | rename_mod(sema, module, new_name) | ||
71 | } | ||
72 | Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { | ||
73 | bail!("Cannot rename builtin type") | ||
74 | } | ||
75 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
76 | def => rename_reference(sema, def, new_name), | ||
77 | } | ||
78 | } | ||
79 | |||
80 | /// Textual range of the identifier which will change when renaming this | ||
81 | /// `Definition`. Note that some definitions, like buitin types, can't be | ||
82 | /// renamed. | ||
83 | pub fn range_for_rename(self, sema: &Semantics<RootDatabase>) -> Option<FileRange> { | ||
84 | // FIXME: the `original_file_range` calls here are wrong -- they never fail, | ||
85 | // and _fall back_ to the entirety of the macro call. Such fall back is | ||
86 | // incorrect for renames. The safe behavior would be to return an error for | ||
87 | // such cases. The correct behavior would be to return an auxiliary list of | ||
88 | // "can't rename these occurrences in macros" items, and then show some kind | ||
89 | // of a dialog to the user. See: | ||
90 | cov_mark::hit!(macros_are_broken_lol); | ||
91 | |||
92 | let res = match self { | ||
93 | Definition::Macro(mac) => { | ||
94 | let src = mac.source(sema.db)?; | ||
95 | let name = match &src.value { | ||
96 | Either::Left(it) => it.name()?, | ||
97 | Either::Right(it) => it.name()?, | ||
98 | }; | ||
99 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
100 | } | ||
101 | Definition::Field(field) => { | ||
102 | let src = field.source(sema.db)?; | ||
103 | |||
104 | match &src.value { | ||
105 | FieldSource::Named(record_field) => { | ||
106 | let name = record_field.name()?; | ||
107 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
108 | } | ||
109 | FieldSource::Pos(_) => { | ||
110 | return None; | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | Definition::ModuleDef(module_def) => match module_def { | ||
115 | hir::ModuleDef::Module(module) => { | ||
116 | let src = module.declaration_source(sema.db)?; | ||
117 | let name = src.value.name()?; | ||
118 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
119 | } | ||
120 | hir::ModuleDef::Function(it) => name_range(it, sema)?, | ||
121 | hir::ModuleDef::Adt(adt) => match adt { | ||
122 | hir::Adt::Struct(it) => name_range(it, sema)?, | ||
123 | hir::Adt::Union(it) => name_range(it, sema)?, | ||
124 | hir::Adt::Enum(it) => name_range(it, sema)?, | ||
125 | }, | ||
126 | hir::ModuleDef::Variant(it) => name_range(it, sema)?, | ||
127 | hir::ModuleDef::Const(it) => name_range(it, sema)?, | ||
128 | hir::ModuleDef::Static(it) => name_range(it, sema)?, | ||
129 | hir::ModuleDef::Trait(it) => name_range(it, sema)?, | ||
130 | hir::ModuleDef::TypeAlias(it) => name_range(it, sema)?, | ||
131 | hir::ModuleDef::BuiltinType(_) => return None, | ||
132 | }, | ||
133 | Definition::SelfType(_) => return None, | ||
134 | Definition::Local(local) => { | ||
135 | let src = local.source(sema.db); | ||
136 | let name = match &src.value { | ||
137 | Either::Left(bind_pat) => bind_pat.name()?, | ||
138 | Either::Right(_) => return None, | ||
139 | }; | ||
140 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
141 | } | ||
142 | Definition::GenericParam(generic_param) => match generic_param { | ||
143 | hir::GenericParam::TypeParam(type_param) => { | ||
144 | let src = type_param.source(sema.db)?; | ||
145 | let name = match &src.value { | ||
146 | Either::Left(_) => return None, | ||
147 | Either::Right(type_param) => type_param.name()?, | ||
148 | }; | ||
149 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
150 | } | ||
151 | hir::GenericParam::LifetimeParam(lifetime_param) => { | ||
152 | let src = lifetime_param.source(sema.db)?; | ||
153 | let lifetime = src.value.lifetime()?; | ||
154 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
155 | } | ||
156 | hir::GenericParam::ConstParam(it) => name_range(it, sema)?, | ||
157 | }, | ||
158 | Definition::Label(label) => { | ||
159 | let src = label.source(sema.db); | ||
160 | let lifetime = src.value.lifetime()?; | ||
161 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
162 | } | ||
163 | }; | ||
164 | return Some(res); | ||
165 | |||
166 | fn name_range<D>(def: D, sema: &Semantics<RootDatabase>) -> Option<FileRange> | ||
167 | where | ||
168 | D: HasSource, | ||
169 | D::Ast: ast::NameOwner, | ||
170 | { | ||
171 | let src = def.source(sema.db)?; | ||
172 | let name = src.value.name()?; | ||
173 | let res = src.with_value(name.syntax()).original_file_range(sema.db); | ||
174 | Some(res) | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | fn rename_mod( | ||
180 | sema: &Semantics<RootDatabase>, | ||
181 | module: hir::Module, | ||
182 | new_name: &str, | ||
183 | ) -> Result<SourceChange> { | ||
184 | if IdentifierKind::classify(new_name)? != IdentifierKind::Ident { | ||
185 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
186 | } | ||
187 | |||
188 | let mut source_change = SourceChange::default(); | ||
189 | |||
190 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); | ||
191 | let file_id = file_id.original_file(sema.db); | ||
192 | if let ModuleSource::SourceFile(..) = def_source { | ||
193 | // mod is defined in path/to/dir/mod.rs | ||
194 | let path = if module.is_mod_rs(sema.db) { | ||
195 | format!("../{}/mod.rs", new_name) | ||
196 | } else { | ||
197 | format!("{}.rs", new_name) | ||
198 | }; | ||
199 | let dst = AnchoredPathBuf { anchor: file_id, path }; | ||
200 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | ||
201 | source_change.push_file_system_edit(move_file); | ||
202 | } | ||
203 | |||
204 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { | ||
205 | let file_id = file_id.original_file(sema.db); | ||
206 | match decl_source.name() { | ||
207 | Some(name) => source_change.insert_source_edit( | ||
208 | file_id, | ||
209 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | ||
210 | ), | ||
211 | _ => never!("Module source node is missing a name"), | ||
212 | } | ||
213 | } | ||
214 | let def = Definition::ModuleDef(hir::ModuleDef::Module(module)); | ||
215 | let usages = def.usages(sema).all(); | ||
216 | let ref_edits = usages.iter().map(|(&file_id, references)| { | ||
217 | (file_id, source_edit_from_references(references, def, new_name)) | ||
218 | }); | ||
219 | source_change.extend(ref_edits); | ||
220 | |||
221 | Ok(source_change) | ||
222 | } | ||
223 | |||
224 | fn rename_reference( | ||
225 | sema: &Semantics<RootDatabase>, | ||
226 | mut def: Definition, | ||
227 | new_name: &str, | ||
228 | ) -> Result<SourceChange> { | ||
229 | let ident_kind = IdentifierKind::classify(new_name)?; | ||
230 | |||
231 | if matches!( | ||
232 | def, // is target a lifetime? | ||
233 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
234 | ) { | ||
235 | match ident_kind { | ||
236 | IdentifierKind::Ident | IdentifierKind::Underscore => { | ||
237 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
238 | bail!("Invalid name `{}`: not a lifetime identifier", new_name); | ||
239 | } | ||
240 | IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), | ||
241 | } | ||
242 | } else { | ||
243 | match (ident_kind, def) { | ||
244 | (IdentifierKind::Lifetime, _) => { | ||
245 | cov_mark::hit!(rename_not_an_ident_ref); | ||
246 | bail!("Invalid name `{}`: not an identifier", new_name); | ||
247 | } | ||
248 | (IdentifierKind::Ident, _) => cov_mark::hit!(rename_non_local), | ||
249 | (IdentifierKind::Underscore, _) => (), | ||
250 | } | ||
251 | } | ||
252 | |||
253 | def = match def { | ||
254 | // HACK: resolve trait impl items to the item def of the trait definition | ||
255 | // so that we properly resolve all trait item references | ||
256 | Definition::ModuleDef(mod_def) => mod_def | ||
257 | .as_assoc_item(sema.db) | ||
258 | .and_then(|it| it.containing_trait_impl(sema.db)) | ||
259 | .and_then(|it| { | ||
260 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { | ||
261 | (hir::AssocItem::Function(trait_func), hir::ModuleDef::Function(func)) | ||
262 | if trait_func.name(sema.db) == func.name(sema.db) => | ||
263 | { | ||
264 | Some(Definition::ModuleDef(hir::ModuleDef::Function(trait_func))) | ||
265 | } | ||
266 | (hir::AssocItem::Const(trait_konst), hir::ModuleDef::Const(konst)) | ||
267 | if trait_konst.name(sema.db) == konst.name(sema.db) => | ||
268 | { | ||
269 | Some(Definition::ModuleDef(hir::ModuleDef::Const(trait_konst))) | ||
270 | } | ||
271 | ( | ||
272 | hir::AssocItem::TypeAlias(trait_type_alias), | ||
273 | hir::ModuleDef::TypeAlias(type_alias), | ||
274 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { | ||
275 | Some(Definition::ModuleDef(hir::ModuleDef::TypeAlias(trait_type_alias))) | ||
276 | } | ||
277 | _ => None, | ||
278 | }) | ||
279 | }) | ||
280 | .unwrap_or(def), | ||
281 | _ => def, | ||
282 | }; | ||
283 | let usages = def.usages(sema).all(); | ||
284 | |||
285 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
286 | cov_mark::hit!(rename_underscore_multiple); | ||
287 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
288 | } | ||
289 | let mut source_change = SourceChange::default(); | ||
290 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
291 | (file_id, source_edit_from_references(references, def, new_name)) | ||
292 | })); | ||
293 | |||
294 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
295 | source_change.insert_source_edit(file_id, edit); | ||
296 | Ok(source_change) | ||
297 | } | ||
298 | |||
299 | pub fn source_edit_from_references( | ||
300 | references: &[FileReference], | ||
301 | def: Definition, | ||
302 | new_name: &str, | ||
303 | ) -> TextEdit { | ||
304 | let mut edit = TextEdit::builder(); | ||
305 | for reference in references { | ||
306 | let (range, replacement) = match &reference.name { | ||
307 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
308 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
309 | // the macro input | ||
310 | ast::NameLike::NameRef(name_ref) | ||
311 | if name_ref.syntax().text_range() == reference.range => | ||
312 | { | ||
313 | source_edit_from_name_ref(name_ref, new_name, def) | ||
314 | } | ||
315 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
316 | source_edit_from_name(name, new_name) | ||
317 | } | ||
318 | _ => None, | ||
319 | } | ||
320 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
321 | edit.replace(range, replacement); | ||
322 | } | ||
323 | edit.finish() | ||
324 | } | ||
325 | |||
326 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
327 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
328 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
329 | return Some(( | ||
330 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
331 | [new_name, ": "].concat(), | ||
332 | )); | ||
333 | } | ||
334 | } | ||
335 | None | ||
336 | } | ||
337 | |||
338 | fn source_edit_from_name_ref( | ||
339 | name_ref: &ast::NameRef, | ||
340 | new_name: &str, | ||
341 | def: Definition, | ||
342 | ) -> Option<(TextRange, String)> { | ||
343 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
344 | let rcf_name_ref = record_field.name_ref(); | ||
345 | let rcf_expr = record_field.expr(); | ||
346 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
347 | // field: init-expr, check if we can use a field init shorthand | ||
348 | (Some(field_name), Some(init)) => { | ||
349 | if field_name == *name_ref { | ||
350 | if init.text() == new_name { | ||
351 | cov_mark::hit!(test_rename_field_put_init_shorthand); | ||
352 | // same names, we can use a shorthand here instead. | ||
353 | // we do not want to erase attributes hence this range start | ||
354 | let s = field_name.syntax().text_range().start(); | ||
355 | let e = record_field.syntax().text_range().end(); | ||
356 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
357 | } | ||
358 | } else if init == *name_ref { | ||
359 | if field_name.text() == new_name { | ||
360 | cov_mark::hit!(test_rename_local_put_init_shorthand); | ||
361 | // same names, we can use a shorthand here instead. | ||
362 | // we do not want to erase attributes hence this range start | ||
363 | let s = field_name.syntax().text_range().start(); | ||
364 | let e = record_field.syntax().text_range().end(); | ||
365 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
366 | } | ||
367 | } | ||
368 | None | ||
369 | } | ||
370 | // init shorthand | ||
371 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
372 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
373 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
374 | cov_mark::hit!(test_rename_field_in_field_shorthand); | ||
375 | let s = name_ref.syntax().text_range().start(); | ||
376 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
377 | } | ||
378 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
379 | cov_mark::hit!(test_rename_local_in_field_shorthand); | ||
380 | let s = name_ref.syntax().text_range().end(); | ||
381 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
382 | } | ||
383 | _ => None, | ||
384 | } | ||
385 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
386 | let rcf_name_ref = record_field.name_ref(); | ||
387 | let rcf_pat = record_field.pat(); | ||
388 | match (rcf_name_ref, rcf_pat) { | ||
389 | // field: rename | ||
390 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
391 | // field name is being renamed | ||
392 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
393 | cov_mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
394 | // same names, we can use a shorthand here instead/ | ||
395 | // we do not want to erase attributes hence this range start | ||
396 | let s = field_name.syntax().text_range().start(); | ||
397 | let e = record_field.syntax().text_range().end(); | ||
398 | Some((TextRange::new(s, e), pat.to_string())) | ||
399 | } else { | ||
400 | None | ||
401 | } | ||
402 | } | ||
403 | _ => None, | ||
404 | } | ||
405 | } else { | ||
406 | None | ||
407 | } | ||
408 | } | ||
409 | |||
410 | fn source_edit_from_def( | ||
411 | sema: &Semantics<RootDatabase>, | ||
412 | def: Definition, | ||
413 | new_name: &str, | ||
414 | ) -> Result<(FileId, TextEdit)> { | ||
415 | let frange = def | ||
416 | .range_for_rename(sema) | ||
417 | .ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
418 | |||
419 | let mut replacement_text = String::new(); | ||
420 | let mut repl_range = frange.range; | ||
421 | if let Definition::Local(local) = def { | ||
422 | if let Either::Left(pat) = local.source(sema.db).value { | ||
423 | if matches!( | ||
424 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
425 | Some(pat_field) if pat_field.name_ref().is_none() | ||
426 | ) { | ||
427 | replacement_text.push_str(": "); | ||
428 | replacement_text.push_str(new_name); | ||
429 | repl_range = TextRange::new( | ||
430 | pat.syntax().text_range().end(), | ||
431 | pat.syntax().text_range().end(), | ||
432 | ); | ||
433 | } | ||
434 | } | ||
435 | } | ||
436 | if replacement_text.is_empty() { | ||
437 | replacement_text.push_str(new_name); | ||
438 | } | ||
439 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
440 | Ok((frange.file_id, edit)) | ||
441 | } | ||
442 | |||
443 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
444 | pub enum IdentifierKind { | ||
445 | Ident, | ||
446 | Lifetime, | ||
447 | Underscore, | ||
448 | } | ||
449 | |||
450 | impl IdentifierKind { | ||
451 | pub fn classify(new_name: &str) -> Result<IdentifierKind> { | ||
452 | match lex_single_syntax_kind(new_name) { | ||
453 | Some(res) => match res { | ||
454 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
455 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
456 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
457 | Ok(IdentifierKind::Lifetime) | ||
458 | } | ||
459 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
460 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
461 | } | ||
462 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
463 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
464 | }, | ||
465 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
466 | } | ||
467 | } | ||
468 | } | ||
diff --git a/crates/ide_diagnostics/Cargo.toml b/crates/ide_diagnostics/Cargo.toml new file mode 100644 index 000000000..fa2adf212 --- /dev/null +++ b/crates/ide_diagnostics/Cargo.toml | |||
@@ -0,0 +1,29 @@ | |||
1 | [package] | ||
2 | name = "ide_diagnostics" | ||
3 | version = "0.0.0" | ||
4 | description = "TBD" | ||
5 | license = "MIT OR Apache-2.0" | ||
6 | authors = ["rust-analyzer developers"] | ||
7 | edition = "2018" | ||
8 | |||
9 | [lib] | ||
10 | doctest = false | ||
11 | |||
12 | [dependencies] | ||
13 | cov-mark = "2.0.0-pre.1" | ||
14 | itertools = "0.10.0" | ||
15 | rustc-hash = "1.1.0" | ||
16 | either = "1.5.3" | ||
17 | |||
18 | profile = { path = "../profile", version = "0.0.0" } | ||
19 | stdx = { path = "../stdx", version = "0.0.0" } | ||
20 | syntax = { path = "../syntax", version = "0.0.0" } | ||
21 | text_edit = { path = "../text_edit", version = "0.0.0" } | ||
22 | cfg = { path = "../cfg", version = "0.0.0" } | ||
23 | hir = { path = "../hir", version = "0.0.0" } | ||
24 | ide_db = { path = "../ide_db", version = "0.0.0" } | ||
25 | |||
26 | [dev-dependencies] | ||
27 | expect-test = "1.1" | ||
28 | |||
29 | test_utils = { path = "../test_utils" } | ||
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide_diagnostics/src/field_shorthand.rs index c7f4dab8e..0b6af9965 100644 --- a/crates/ide/src/diagnostics/field_shorthand.rs +++ b/crates/ide_diagnostics/src/field_shorthand.rs | |||
@@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange}; | |||
5 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | 5 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; |
6 | use text_edit::TextEdit; | 6 | use text_edit::TextEdit; |
7 | 7 | ||
8 | use crate::{diagnostics::fix, Diagnostic, Severity}; | 8 | use crate::{fix, Diagnostic, Severity}; |
9 | 9 | ||
10 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | 10 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { |
11 | match_ast! { | 11 | match_ast! { |
@@ -101,7 +101,7 @@ fn check_pat_field_shorthand( | |||
101 | 101 | ||
102 | #[cfg(test)] | 102 | #[cfg(test)] |
103 | mod tests { | 103 | mod tests { |
104 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 104 | use crate::tests::{check_diagnostics, check_fix}; |
105 | 105 | ||
106 | #[test] | 106 | #[test] |
107 | fn test_check_expr_field_shorthand() { | 107 | fn test_check_expr_field_shorthand() { |
diff --git a/crates/ide/src/diagnostics/break_outside_of_loop.rs b/crates/ide_diagnostics/src/handlers/break_outside_of_loop.rs index 80e68f3cc..5ad0fbd1b 100644 --- a/crates/ide/src/diagnostics/break_outside_of_loop.rs +++ b/crates/ide_diagnostics/src/handlers/break_outside_of_loop.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: break-outside-of-loop | 3 | // Diagnostic: break-outside-of-loop |
4 | // | 4 | // |
5 | // This diagnostic is triggered if the `break` keyword is used outside of a loop. | 5 | // This diagnostic is triggered if the `break` keyword is used outside of a loop. |
6 | pub(super) fn break_outside_of_loop( | 6 | pub(crate) fn break_outside_of_loop( |
7 | ctx: &DiagnosticsContext<'_>, | 7 | ctx: &DiagnosticsContext<'_>, |
8 | d: &hir::BreakOutsideOfLoop, | 8 | d: &hir::BreakOutsideOfLoop, |
9 | ) -> Diagnostic { | 9 | ) -> Diagnostic { |
@@ -16,7 +16,7 @@ pub(super) fn break_outside_of_loop( | |||
16 | 16 | ||
17 | #[cfg(test)] | 17 | #[cfg(test)] |
18 | mod tests { | 18 | mod tests { |
19 | use crate::diagnostics::tests::check_diagnostics; | 19 | use crate::tests::check_diagnostics; |
20 | 20 | ||
21 | #[test] | 21 | #[test] |
22 | fn break_outside_of_loop() { | 22 | fn break_outside_of_loop() { |
diff --git a/crates/ide/src/diagnostics/inactive_code.rs b/crates/ide_diagnostics/src/handlers/inactive_code.rs index d9d3e88c1..4b722fd64 100644 --- a/crates/ide/src/diagnostics/inactive_code.rs +++ b/crates/ide_diagnostics/src/handlers/inactive_code.rs | |||
@@ -1,15 +1,12 @@ | |||
1 | use cfg::DnfExpr; | 1 | use cfg::DnfExpr; |
2 | use stdx::format_to; | 2 | use stdx::format_to; |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{Diagnostic, DiagnosticsContext, Severity}; |
5 | diagnostics::{Diagnostic, DiagnosticsContext}, | ||
6 | Severity, | ||
7 | }; | ||
8 | 5 | ||
9 | // Diagnostic: inactive-code | 6 | // Diagnostic: inactive-code |
10 | // | 7 | // |
11 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. | 8 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. |
12 | pub(super) fn inactive_code( | 9 | pub(crate) fn inactive_code( |
13 | ctx: &DiagnosticsContext<'_>, | 10 | ctx: &DiagnosticsContext<'_>, |
14 | d: &hir::InactiveCode, | 11 | d: &hir::InactiveCode, |
15 | ) -> Option<Diagnostic> { | 12 | ) -> Option<Diagnostic> { |
@@ -37,7 +34,7 @@ pub(super) fn inactive_code( | |||
37 | 34 | ||
38 | #[cfg(test)] | 35 | #[cfg(test)] |
39 | mod tests { | 36 | mod tests { |
40 | use crate::{diagnostics::tests::check_diagnostics_with_config, DiagnosticsConfig}; | 37 | use crate::{tests::check_diagnostics_with_config, DiagnosticsConfig}; |
41 | 38 | ||
42 | pub(crate) fn check(ra_fixture: &str) { | 39 | pub(crate) fn check(ra_fixture: &str) { |
43 | let config = DiagnosticsConfig::default(); | 40 | let config = DiagnosticsConfig::default(); |
diff --git a/crates/ide/src/diagnostics/incorrect_case.rs b/crates/ide_diagnostics/src/handlers/incorrect_case.rs index 832394400..3a33029cf 100644 --- a/crates/ide/src/diagnostics/incorrect_case.rs +++ b/crates/ide_diagnostics/src/handlers/incorrect_case.rs | |||
@@ -1,18 +1,19 @@ | |||
1 | use hir::{db::AstDatabase, InFile}; | 1 | use hir::{db::AstDatabase, InFile}; |
2 | use ide_assists::Assist; | 2 | use ide_db::{assists::Assist, defs::NameClass}; |
3 | use ide_db::base_db::FilePosition; | ||
4 | use syntax::AstNode; | 3 | use syntax::AstNode; |
5 | 4 | ||
6 | use crate::{ | 5 | use crate::{ |
7 | diagnostics::{unresolved_fix, Diagnostic, DiagnosticsContext}, | 6 | // references::rename::rename_with_semantics, |
8 | references::rename::rename_with_semantics, | 7 | unresolved_fix, |
8 | Diagnostic, | ||
9 | DiagnosticsContext, | ||
9 | Severity, | 10 | Severity, |
10 | }; | 11 | }; |
11 | 12 | ||
12 | // Diagnostic: incorrect-ident-case | 13 | // Diagnostic: incorrect-ident-case |
13 | // | 14 | // |
14 | // This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention]. | 15 | // This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention]. |
15 | pub(super) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Diagnostic { | 16 | pub(crate) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Diagnostic { |
16 | Diagnostic::new( | 17 | Diagnostic::new( |
17 | "incorrect-ident-case", | 18 | "incorrect-ident-case", |
18 | format!( | 19 | format!( |
@@ -28,15 +29,15 @@ pub(super) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCas | |||
28 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> { | 29 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> { |
29 | let root = ctx.sema.db.parse_or_expand(d.file)?; | 30 | let root = ctx.sema.db.parse_or_expand(d.file)?; |
30 | let name_node = d.ident.to_node(&root); | 31 | let name_node = d.ident.to_node(&root); |
32 | let def = NameClass::classify(&ctx.sema, &name_node)?.defined(ctx.sema.db)?; | ||
31 | 33 | ||
32 | let name_node = InFile::new(d.file, name_node.syntax()); | 34 | let name_node = InFile::new(d.file, name_node.syntax()); |
33 | let frange = name_node.original_file_range(ctx.sema.db); | 35 | let frange = name_node.original_file_range(ctx.sema.db); |
34 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | ||
35 | 36 | ||
36 | let label = format!("Rename to {}", d.suggested_text); | 37 | let label = format!("Rename to {}", d.suggested_text); |
37 | let mut res = unresolved_fix("change_case", &label, frange.range); | 38 | let mut res = unresolved_fix("change_case", &label, frange.range); |
38 | if ctx.resolve.should_resolve(&res.id) { | 39 | if ctx.resolve.should_resolve(&res.id) { |
39 | let source_change = rename_with_semantics(&ctx.sema, file_position, &d.suggested_text); | 40 | let source_change = def.rename(&ctx.sema, &d.suggested_text); |
40 | res.source_change = Some(source_change.ok().unwrap_or_default()); | 41 | res.source_change = Some(source_change.ok().unwrap_or_default()); |
41 | } | 42 | } |
42 | 43 | ||
@@ -45,10 +46,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Ass | |||
45 | 46 | ||
46 | #[cfg(test)] | 47 | #[cfg(test)] |
47 | mod change_case { | 48 | mod change_case { |
48 | use crate::{ | 49 | use crate::tests::{check_diagnostics, check_fix}; |
49 | diagnostics::tests::{check_diagnostics, check_fix}, | ||
50 | fixture, AssistResolveStrategy, DiagnosticsConfig, | ||
51 | }; | ||
52 | 50 | ||
53 | #[test] | 51 | #[test] |
54 | fn test_rename_incorrect_case() { | 52 | fn test_rename_incorrect_case() { |
@@ -116,7 +114,7 @@ fn some_fn() { | |||
116 | check_diagnostics( | 114 | check_diagnostics( |
117 | r#" | 115 | r#" |
118 | fn foo() { | 116 | fn foo() { |
119 | const ANOTHER_ITEM$0: &str = "some_item"; | 117 | const ANOTHER_ITEM: &str = "some_item"; |
120 | } | 118 | } |
121 | "#, | 119 | "#, |
122 | ); | 120 | ); |
@@ -148,20 +146,13 @@ impl TestStruct { | |||
148 | 146 | ||
149 | #[test] | 147 | #[test] |
150 | fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() { | 148 | fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() { |
151 | let input = r#"fn FOO$0() {}"#; | 149 | check_diagnostics( |
152 | let expected = r#"fn foo() {}"#; | 150 | r#" |
153 | 151 | fn FOO() {} | |
154 | let (analysis, file_position) = fixture::position(input); | 152 | // ^^^ Function `FOO` should have snake_case name, e.g. `foo` |
155 | let diagnostics = analysis | 153 | "#, |
156 | .diagnostics( | 154 | ); |
157 | &DiagnosticsConfig::default(), | 155 | check_fix(r#"fn FOO$0() {}"#, r#"fn foo() {}"#); |
158 | AssistResolveStrategy::All, | ||
159 | file_position.file_id, | ||
160 | ) | ||
161 | .unwrap(); | ||
162 | assert_eq!(diagnostics.len(), 1); | ||
163 | |||
164 | check_fix(input, expected); | ||
165 | } | 156 | } |
166 | 157 | ||
167 | #[test] | 158 | #[test] |
diff --git a/crates/ide/src/diagnostics/macro_error.rs b/crates/ide_diagnostics/src/handlers/macro_error.rs index 5f97f190d..d4d928ad1 100644 --- a/crates/ide/src/diagnostics/macro_error.rs +++ b/crates/ide_diagnostics/src/handlers/macro_error.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: macro-error | 3 | // Diagnostic: macro-error |
4 | // | 4 | // |
5 | // This diagnostic is shown for macro expansion errors. | 5 | // This diagnostic is shown for macro expansion errors. |
6 | pub(super) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic { | 6 | pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic { |
7 | Diagnostic::new( | 7 | Diagnostic::new( |
8 | "macro-error", | 8 | "macro-error", |
9 | d.message.clone(), | 9 | d.message.clone(), |
@@ -15,7 +15,7 @@ pub(super) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> | |||
15 | #[cfg(test)] | 15 | #[cfg(test)] |
16 | mod tests { | 16 | mod tests { |
17 | use crate::{ | 17 | use crate::{ |
18 | diagnostics::tests::{check_diagnostics, check_diagnostics_with_config}, | 18 | tests::{check_diagnostics, check_diagnostics_with_config}, |
19 | DiagnosticsConfig, | 19 | DiagnosticsConfig, |
20 | }; | 20 | }; |
21 | 21 | ||
diff --git a/crates/ide/src/diagnostics/mismatched_arg_count.rs b/crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs index 08e1cfa5f..ce313b2cc 100644 --- a/crates/ide/src/diagnostics/mismatched_arg_count.rs +++ b/crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: mismatched-arg-count | 3 | // Diagnostic: mismatched-arg-count |
4 | // | 4 | // |
5 | // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. | 5 | // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. |
6 | pub(super) fn mismatched_arg_count( | 6 | pub(crate) fn mismatched_arg_count( |
7 | ctx: &DiagnosticsContext<'_>, | 7 | ctx: &DiagnosticsContext<'_>, |
8 | d: &hir::MismatchedArgCount, | 8 | d: &hir::MismatchedArgCount, |
9 | ) -> Diagnostic { | 9 | ) -> Diagnostic { |
@@ -18,7 +18,7 @@ pub(super) fn mismatched_arg_count( | |||
18 | 18 | ||
19 | #[cfg(test)] | 19 | #[cfg(test)] |
20 | mod tests { | 20 | mod tests { |
21 | use crate::diagnostics::tests::check_diagnostics; | 21 | use crate::tests::check_diagnostics; |
22 | 22 | ||
23 | #[test] | 23 | #[test] |
24 | fn simple_free_fn_zero() { | 24 | fn simple_free_fn_zero() { |
diff --git a/crates/ide/src/diagnostics/missing_fields.rs b/crates/ide_diagnostics/src/handlers/missing_fields.rs index d01f05041..bc82c0e4a 100644 --- a/crates/ide/src/diagnostics/missing_fields.rs +++ b/crates/ide_diagnostics/src/handlers/missing_fields.rs | |||
@@ -1,12 +1,11 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::{db::AstDatabase, InFile}; | 2 | use hir::{db::AstDatabase, InFile}; |
3 | use ide_assists::Assist; | 3 | use ide_db::{assists::Assist, source_change::SourceChange}; |
4 | use ide_db::source_change::SourceChange; | ||
5 | use stdx::format_to; | 4 | use stdx::format_to; |
6 | use syntax::{algo, ast::make, AstNode, SyntaxNodePtr}; | 5 | use syntax::{algo, ast::make, AstNode, SyntaxNodePtr}; |
7 | use text_edit::TextEdit; | 6 | use text_edit::TextEdit; |
8 | 7 | ||
9 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | 8 | use crate::{fix, Diagnostic, DiagnosticsContext}; |
10 | 9 | ||
11 | // Diagnostic: missing-fields | 10 | // Diagnostic: missing-fields |
12 | // | 11 | // |
@@ -19,7 +18,7 @@ use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | |||
19 | // | 18 | // |
20 | // let a = A { a: 10 }; | 19 | // let a = A { a: 10 }; |
21 | // ``` | 20 | // ``` |
22 | pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic { | 21 | pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic { |
23 | let mut message = String::from("Missing structure fields:\n"); | 22 | let mut message = String::from("Missing structure fields:\n"); |
24 | for field in &d.missed_fields { | 23 | for field in &d.missed_fields { |
25 | format_to!(message, "- {}\n", field); | 24 | format_to!(message, "- {}\n", field); |
@@ -77,7 +76,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass | |||
77 | 76 | ||
78 | #[cfg(test)] | 77 | #[cfg(test)] |
79 | mod tests { | 78 | mod tests { |
80 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 79 | use crate::tests::{check_diagnostics, check_fix}; |
81 | 80 | ||
82 | #[test] | 81 | #[test] |
83 | fn missing_record_pat_field_diagnostic() { | 82 | fn missing_record_pat_field_diagnostic() { |
diff --git a/crates/ide/src/diagnostics/missing_match_arms.rs b/crates/ide_diagnostics/src/handlers/missing_match_arms.rs index b636489b3..9ea533d74 100644 --- a/crates/ide/src/diagnostics/missing_match_arms.rs +++ b/crates/ide_diagnostics/src/handlers/missing_match_arms.rs | |||
@@ -1,11 +1,11 @@ | |||
1 | use hir::InFile; | 1 | use hir::InFile; |
2 | 2 | ||
3 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 3 | use crate::{Diagnostic, DiagnosticsContext}; |
4 | 4 | ||
5 | // Diagnostic: missing-match-arm | 5 | // Diagnostic: missing-match-arm |
6 | // | 6 | // |
7 | // This diagnostic is triggered if `match` block is missing one or more match arms. | 7 | // This diagnostic is triggered if `match` block is missing one or more match arms. |
8 | pub(super) fn missing_match_arms( | 8 | pub(crate) fn missing_match_arms( |
9 | ctx: &DiagnosticsContext<'_>, | 9 | ctx: &DiagnosticsContext<'_>, |
10 | d: &hir::MissingMatchArms, | 10 | d: &hir::MissingMatchArms, |
11 | ) -> Diagnostic { | 11 | ) -> Diagnostic { |
@@ -17,12 +17,12 @@ pub(super) fn missing_match_arms( | |||
17 | } | 17 | } |
18 | 18 | ||
19 | #[cfg(test)] | 19 | #[cfg(test)] |
20 | pub(super) mod tests { | 20 | mod tests { |
21 | use crate::diagnostics::tests::check_diagnostics; | 21 | use crate::tests::check_diagnostics; |
22 | 22 | ||
23 | fn check_diagnostics_no_bails(ra_fixture: &str) { | 23 | fn check_diagnostics_no_bails(ra_fixture: &str) { |
24 | cov_mark::check_count!(validate_match_bailed_out, 0); | 24 | cov_mark::check_count!(validate_match_bailed_out, 0); |
25 | crate::diagnostics::tests::check_diagnostics(ra_fixture) | 25 | crate::tests::check_diagnostics(ra_fixture) |
26 | } | 26 | } |
27 | 27 | ||
28 | #[test] | 28 | #[test] |
diff --git a/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs b/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs index 06005d156..63de54570 100644 --- a/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs +++ b/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use hir::db::AstDatabase; | 1 | use hir::db::AstDatabase; |
2 | use ide_assists::Assist; | 2 | use ide_db::{assists::Assist, source_change::SourceChange}; |
3 | use ide_db::source_change::SourceChange; | ||
4 | use syntax::AstNode; | 3 | use syntax::AstNode; |
5 | use text_edit::TextEdit; | 4 | use text_edit::TextEdit; |
6 | 5 | ||
7 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | 6 | use crate::{fix, Diagnostic, DiagnosticsContext}; |
8 | 7 | ||
9 | // Diagnostic: missing-ok-or-some-in-tail-expr | 8 | // Diagnostic: missing-ok-or-some-in-tail-expr |
10 | // | 9 | // |
@@ -18,7 +17,7 @@ use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | |||
18 | // 10 | 17 | // 10 |
19 | // } | 18 | // } |
20 | // ``` | 19 | // ``` |
21 | pub(super) fn missing_ok_or_some_in_tail_expr( | 20 | pub(crate) fn missing_ok_or_some_in_tail_expr( |
22 | ctx: &DiagnosticsContext<'_>, | 21 | ctx: &DiagnosticsContext<'_>, |
23 | d: &hir::MissingOkOrSomeInTailExpr, | 22 | d: &hir::MissingOkOrSomeInTailExpr, |
24 | ) -> Diagnostic { | 23 | ) -> Diagnostic { |
@@ -44,7 +43,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Op | |||
44 | 43 | ||
45 | #[cfg(test)] | 44 | #[cfg(test)] |
46 | mod tests { | 45 | mod tests { |
47 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 46 | use crate::tests::{check_diagnostics, check_fix}; |
48 | 47 | ||
49 | #[test] | 48 | #[test] |
50 | fn test_wrap_return_type_option() { | 49 | fn test_wrap_return_type_option() { |
diff --git a/crates/ide/src/diagnostics/missing_unsafe.rs b/crates/ide_diagnostics/src/handlers/missing_unsafe.rs index 5c47e8d0a..62d8687ba 100644 --- a/crates/ide/src/diagnostics/missing_unsafe.rs +++ b/crates/ide_diagnostics/src/handlers/missing_unsafe.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: missing-unsafe | 3 | // Diagnostic: missing-unsafe |
4 | // | 4 | // |
5 | // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. | 5 | // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. |
6 | pub(super) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic { | 6 | pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic { |
7 | Diagnostic::new( | 7 | Diagnostic::new( |
8 | "missing-unsafe", | 8 | "missing-unsafe", |
9 | "this operation is unsafe and requires an unsafe function or block", | 9 | "this operation is unsafe and requires an unsafe function or block", |
@@ -13,7 +13,7 @@ pub(super) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf | |||
13 | 13 | ||
14 | #[cfg(test)] | 14 | #[cfg(test)] |
15 | mod tests { | 15 | mod tests { |
16 | use crate::diagnostics::tests::check_diagnostics; | 16 | use crate::tests::check_diagnostics; |
17 | 17 | ||
18 | #[test] | 18 | #[test] |
19 | fn missing_unsafe_diagnostic_with_raw_ptr() { | 19 | fn missing_unsafe_diagnostic_with_raw_ptr() { |
diff --git a/crates/ide/src/diagnostics/no_such_field.rs b/crates/ide_diagnostics/src/handlers/no_such_field.rs index edc63c246..e4cc8a840 100644 --- a/crates/ide/src/diagnostics/no_such_field.rs +++ b/crates/ide_diagnostics/src/handlers/no_such_field.rs | |||
@@ -6,15 +6,12 @@ use syntax::{ | |||
6 | }; | 6 | }; |
7 | use text_edit::TextEdit; | 7 | use text_edit::TextEdit; |
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; |
10 | diagnostics::{fix, Diagnostic, DiagnosticsContext}, | ||
11 | Assist, | ||
12 | }; | ||
13 | 10 | ||
14 | // Diagnostic: no-such-field | 11 | // Diagnostic: no-such-field |
15 | // | 12 | // |
16 | // This diagnostic is triggered if created structure does not have field provided in record. | 13 | // This diagnostic is triggered if created structure does not have field provided in record. |
17 | pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { | 14 | pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { |
18 | Diagnostic::new( | 15 | Diagnostic::new( |
19 | "no-such-field", | 16 | "no-such-field", |
20 | "no such field", | 17 | "no such field", |
@@ -112,7 +109,7 @@ fn missing_record_expr_field_fixes( | |||
112 | 109 | ||
113 | #[cfg(test)] | 110 | #[cfg(test)] |
114 | mod tests { | 111 | mod tests { |
115 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 112 | use crate::tests::{check_diagnostics, check_fix}; |
116 | 113 | ||
117 | #[test] | 114 | #[test] |
118 | fn no_such_field_diagnostics() { | 115 | fn no_such_field_diagnostics() { |
diff --git a/crates/ide/src/diagnostics/remove_this_semicolon.rs b/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs index 814cb0f8c..b52e4dc84 100644 --- a/crates/ide/src/diagnostics/remove_this_semicolon.rs +++ b/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs | |||
@@ -3,15 +3,12 @@ use ide_db::source_change::SourceChange; | |||
3 | use syntax::{ast, AstNode}; | 3 | use syntax::{ast, AstNode}; |
4 | use text_edit::TextEdit; | 4 | use text_edit::TextEdit; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; |
7 | diagnostics::{fix, Diagnostic, DiagnosticsContext}, | ||
8 | Assist, | ||
9 | }; | ||
10 | 7 | ||
11 | // Diagnostic: remove-this-semicolon | 8 | // Diagnostic: remove-this-semicolon |
12 | // | 9 | // |
13 | // This diagnostic is triggered when there's an erroneous `;` at the end of the block. | 10 | // This diagnostic is triggered when there's an erroneous `;` at the end of the block. |
14 | pub(super) fn remove_this_semicolon( | 11 | pub(crate) fn remove_this_semicolon( |
15 | ctx: &DiagnosticsContext<'_>, | 12 | ctx: &DiagnosticsContext<'_>, |
16 | d: &hir::RemoveThisSemicolon, | 13 | d: &hir::RemoveThisSemicolon, |
17 | ) -> Diagnostic { | 14 | ) -> Diagnostic { |
@@ -45,7 +42,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::RemoveThisSemicolon) -> Option<V | |||
45 | 42 | ||
46 | #[cfg(test)] | 43 | #[cfg(test)] |
47 | mod tests { | 44 | mod tests { |
48 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 45 | use crate::tests::{check_diagnostics, check_fix}; |
49 | 46 | ||
50 | #[test] | 47 | #[test] |
51 | fn missing_semicolon() { | 48 | fn missing_semicolon() { |
diff --git a/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs b/crates/ide_diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs index f3b011495..10d5da15d 100644 --- a/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs +++ b/crates/ide_diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs | |||
@@ -6,15 +6,12 @@ use syntax::{ | |||
6 | }; | 6 | }; |
7 | use text_edit::TextEdit; | 7 | use text_edit::TextEdit; |
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity}; |
10 | diagnostics::{fix, Diagnostic, DiagnosticsContext}, | ||
11 | Assist, Severity, | ||
12 | }; | ||
13 | 10 | ||
14 | // Diagnostic: replace-filter-map-next-with-find-map | 11 | // Diagnostic: replace-filter-map-next-with-find-map |
15 | // | 12 | // |
16 | // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. | 13 | // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. |
17 | pub(super) fn replace_filter_map_next_with_find_map( | 14 | pub(crate) fn replace_filter_map_next_with_find_map( |
18 | ctx: &DiagnosticsContext<'_>, | 15 | ctx: &DiagnosticsContext<'_>, |
19 | d: &hir::ReplaceFilterMapNextWithFindMap, | 16 | d: &hir::ReplaceFilterMapNextWithFindMap, |
20 | ) -> Diagnostic { | 17 | ) -> Diagnostic { |
@@ -58,7 +55,7 @@ fn fixes( | |||
58 | 55 | ||
59 | #[cfg(test)] | 56 | #[cfg(test)] |
60 | mod tests { | 57 | mod tests { |
61 | use crate::diagnostics::tests::check_fix; | 58 | use crate::tests::check_fix; |
62 | 59 | ||
63 | // Register the required standard library types to make the tests work | 60 | // Register the required standard library types to make the tests work |
64 | #[track_caller] | 61 | #[track_caller] |
@@ -86,7 +83,7 @@ pub mod iter { | |||
86 | } | 83 | } |
87 | } | 84 | } |
88 | "#; | 85 | "#; |
89 | crate::diagnostics::tests::check_diagnostics(&format!("{}{}{}", prefix, ra_fixture, suffix)) | 86 | crate::tests::check_diagnostics(&format!("{}{}{}", prefix, ra_fixture, suffix)) |
90 | } | 87 | } |
91 | 88 | ||
92 | #[test] | 89 | #[test] |
diff --git a/crates/ide/src/diagnostics/unimplemented_builtin_macro.rs b/crates/ide_diagnostics/src/handlers/unimplemented_builtin_macro.rs index 09faa3bbc..e879de75c 100644 --- a/crates/ide/src/diagnostics/unimplemented_builtin_macro.rs +++ b/crates/ide_diagnostics/src/handlers/unimplemented_builtin_macro.rs | |||
@@ -1,12 +1,9 @@ | |||
1 | use crate::{ | 1 | use crate::{Diagnostic, DiagnosticsContext, Severity}; |
2 | diagnostics::{Diagnostic, DiagnosticsContext}, | ||
3 | Severity, | ||
4 | }; | ||
5 | 2 | ||
6 | // Diagnostic: unimplemented-builtin-macro | 3 | // Diagnostic: unimplemented-builtin-macro |
7 | // | 4 | // |
8 | // This diagnostic is shown for builtin macros which are not yet implemented by rust-analyzer | 5 | // This diagnostic is shown for builtin macros which are not yet implemented by rust-analyzer |
9 | pub(super) fn unimplemented_builtin_macro( | 6 | pub(crate) fn unimplemented_builtin_macro( |
10 | ctx: &DiagnosticsContext<'_>, | 7 | ctx: &DiagnosticsContext<'_>, |
11 | d: &hir::UnimplementedBuiltinMacro, | 8 | d: &hir::UnimplementedBuiltinMacro, |
12 | ) -> Diagnostic { | 9 | ) -> Diagnostic { |
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide_diagnostics/src/handlers/unlinked_file.rs index a5b2e3399..8921ddde2 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide_diagnostics/src/handlers/unlinked_file.rs | |||
@@ -12,10 +12,7 @@ use syntax::{ | |||
12 | }; | 12 | }; |
13 | use text_edit::TextEdit; | 13 | use text_edit::TextEdit; |
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; |
16 | diagnostics::{fix, DiagnosticsContext}, | ||
17 | Assist, Diagnostic, | ||
18 | }; | ||
19 | 16 | ||
20 | #[derive(Debug)] | 17 | #[derive(Debug)] |
21 | pub(crate) struct UnlinkedFile { | 18 | pub(crate) struct UnlinkedFile { |
@@ -26,7 +23,7 @@ pub(crate) struct UnlinkedFile { | |||
26 | // | 23 | // |
27 | // This diagnostic is shown for files that are not included in any crate, or files that are part of | 24 | // This diagnostic is shown for files that are not included in any crate, or files that are part of |
28 | // crates rust-analyzer failed to discover. The file will not have IDE features available. | 25 | // crates rust-analyzer failed to discover. The file will not have IDE features available. |
29 | pub(super) fn unlinked_file(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Diagnostic { | 26 | pub(crate) fn unlinked_file(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Diagnostic { |
30 | // Limit diagnostic to the first few characters in the file. This matches how VS Code | 27 | // Limit diagnostic to the first few characters in the file. This matches how VS Code |
31 | // renders it with the full span, but on other editors, and is less invasive. | 28 | // renders it with the full span, but on other editors, and is less invasive. |
32 | let range = ctx.sema.db.parse(d.file).syntax_node().text_range(); | 29 | let range = ctx.sema.db.parse(d.file).syntax_node().text_range(); |
@@ -164,7 +161,7 @@ fn make_fixes( | |||
164 | 161 | ||
165 | #[cfg(test)] | 162 | #[cfg(test)] |
166 | mod tests { | 163 | mod tests { |
167 | use crate::diagnostics::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix}; | 164 | use crate::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix}; |
168 | 165 | ||
169 | #[test] | 166 | #[test] |
170 | fn unlinked_file_prepend_first_item() { | 167 | fn unlinked_file_prepend_first_item() { |
diff --git a/crates/ide/src/diagnostics/unresolved_extern_crate.rs b/crates/ide_diagnostics/src/handlers/unresolved_extern_crate.rs index 2ea79c2ee..f5313cc0c 100644 --- a/crates/ide/src/diagnostics/unresolved_extern_crate.rs +++ b/crates/ide_diagnostics/src/handlers/unresolved_extern_crate.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: unresolved-extern-crate | 3 | // Diagnostic: unresolved-extern-crate |
4 | // | 4 | // |
5 | // This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate. | 5 | // This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate. |
6 | pub(super) fn unresolved_extern_crate( | 6 | pub(crate) fn unresolved_extern_crate( |
7 | ctx: &DiagnosticsContext<'_>, | 7 | ctx: &DiagnosticsContext<'_>, |
8 | d: &hir::UnresolvedExternCrate, | 8 | d: &hir::UnresolvedExternCrate, |
9 | ) -> Diagnostic { | 9 | ) -> Diagnostic { |
@@ -16,7 +16,7 @@ pub(super) fn unresolved_extern_crate( | |||
16 | 16 | ||
17 | #[cfg(test)] | 17 | #[cfg(test)] |
18 | mod tests { | 18 | mod tests { |
19 | use crate::diagnostics::tests::check_diagnostics; | 19 | use crate::tests::check_diagnostics; |
20 | 20 | ||
21 | #[test] | 21 | #[test] |
22 | fn unresolved_extern_crate() { | 22 | fn unresolved_extern_crate() { |
diff --git a/crates/ide/src/diagnostics/unresolved_import.rs b/crates/ide_diagnostics/src/handlers/unresolved_import.rs index 1cbf96ba1..f30051c12 100644 --- a/crates/ide/src/diagnostics/unresolved_import.rs +++ b/crates/ide_diagnostics/src/handlers/unresolved_import.rs | |||
@@ -1,10 +1,10 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: unresolved-import | 3 | // Diagnostic: unresolved-import |
4 | // | 4 | // |
5 | // This diagnostic is triggered if rust-analyzer is unable to resolve a path in | 5 | // This diagnostic is triggered if rust-analyzer is unable to resolve a path in |
6 | // a `use` declaration. | 6 | // a `use` declaration. |
7 | pub(super) fn unresolved_import( | 7 | pub(crate) fn unresolved_import( |
8 | ctx: &DiagnosticsContext<'_>, | 8 | ctx: &DiagnosticsContext<'_>, |
9 | d: &hir::UnresolvedImport, | 9 | d: &hir::UnresolvedImport, |
10 | ) -> Diagnostic { | 10 | ) -> Diagnostic { |
@@ -22,7 +22,7 @@ pub(super) fn unresolved_import( | |||
22 | 22 | ||
23 | #[cfg(test)] | 23 | #[cfg(test)] |
24 | mod tests { | 24 | mod tests { |
25 | use crate::diagnostics::tests::check_diagnostics; | 25 | use crate::tests::check_diagnostics; |
26 | 26 | ||
27 | #[test] | 27 | #[test] |
28 | fn unresolved_import() { | 28 | fn unresolved_import() { |
diff --git a/crates/ide/src/diagnostics/unresolved_macro_call.rs b/crates/ide_diagnostics/src/handlers/unresolved_macro_call.rs index 15b6a2730..4c3c1c19a 100644 --- a/crates/ide/src/diagnostics/unresolved_macro_call.rs +++ b/crates/ide_diagnostics/src/handlers/unresolved_macro_call.rs | |||
@@ -1,13 +1,13 @@ | |||
1 | use hir::{db::AstDatabase, InFile}; | 1 | use hir::{db::AstDatabase, InFile}; |
2 | use syntax::{AstNode, SyntaxNodePtr}; | 2 | use syntax::{AstNode, SyntaxNodePtr}; |
3 | 3 | ||
4 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 4 | use crate::{Diagnostic, DiagnosticsContext}; |
5 | 5 | ||
6 | // Diagnostic: unresolved-macro-call | 6 | // Diagnostic: unresolved-macro-call |
7 | // | 7 | // |
8 | // This diagnostic is triggered if rust-analyzer is unable to resolve the path | 8 | // This diagnostic is triggered if rust-analyzer is unable to resolve the path |
9 | // to a macro in a macro invocation. | 9 | // to a macro in a macro invocation. |
10 | pub(super) fn unresolved_macro_call( | 10 | pub(crate) fn unresolved_macro_call( |
11 | ctx: &DiagnosticsContext<'_>, | 11 | ctx: &DiagnosticsContext<'_>, |
12 | d: &hir::UnresolvedMacroCall, | 12 | d: &hir::UnresolvedMacroCall, |
13 | ) -> Diagnostic { | 13 | ) -> Diagnostic { |
@@ -32,7 +32,7 @@ pub(super) fn unresolved_macro_call( | |||
32 | 32 | ||
33 | #[cfg(test)] | 33 | #[cfg(test)] |
34 | mod tests { | 34 | mod tests { |
35 | use crate::diagnostics::tests::check_diagnostics; | 35 | use crate::tests::check_diagnostics; |
36 | 36 | ||
37 | #[test] | 37 | #[test] |
38 | fn unresolved_macro_diag() { | 38 | fn unresolved_macro_diag() { |
diff --git a/crates/ide/src/diagnostics/unresolved_module.rs b/crates/ide_diagnostics/src/handlers/unresolved_module.rs index 977b46414..17166a0c6 100644 --- a/crates/ide/src/diagnostics/unresolved_module.rs +++ b/crates/ide_diagnostics/src/handlers/unresolved_module.rs | |||
@@ -1,14 +1,13 @@ | |||
1 | use hir::db::AstDatabase; | 1 | use hir::db::AstDatabase; |
2 | use ide_assists::Assist; | 2 | use ide_db::{assists::Assist, base_db::AnchoredPathBuf, source_change::FileSystemEdit}; |
3 | use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit}; | ||
4 | use syntax::AstNode; | 3 | use syntax::AstNode; |
5 | 4 | ||
6 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | 5 | use crate::{fix, Diagnostic, DiagnosticsContext}; |
7 | 6 | ||
8 | // Diagnostic: unresolved-module | 7 | // Diagnostic: unresolved-module |
9 | // | 8 | // |
10 | // This diagnostic is triggered if rust-analyzer is unable to discover referred module. | 9 | // This diagnostic is triggered if rust-analyzer is unable to discover referred module. |
11 | pub(super) fn unresolved_module( | 10 | pub(crate) fn unresolved_module( |
12 | ctx: &DiagnosticsContext<'_>, | 11 | ctx: &DiagnosticsContext<'_>, |
13 | d: &hir::UnresolvedModule, | 12 | d: &hir::UnresolvedModule, |
14 | ) -> Diagnostic { | 13 | ) -> Diagnostic { |
@@ -42,7 +41,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Option<Vec< | |||
42 | mod tests { | 41 | mod tests { |
43 | use expect_test::expect; | 42 | use expect_test::expect; |
44 | 43 | ||
45 | use crate::diagnostics::tests::{check_diagnostics, check_expect}; | 44 | use crate::tests::{check_diagnostics, check_expect}; |
46 | 45 | ||
47 | #[test] | 46 | #[test] |
48 | fn unresolved_module() { | 47 | fn unresolved_module() { |
diff --git a/crates/ide/src/diagnostics/unresolved_proc_macro.rs b/crates/ide_diagnostics/src/handlers/unresolved_proc_macro.rs index 3dc6ab451..fde1d1323 100644 --- a/crates/ide/src/diagnostics/unresolved_proc_macro.rs +++ b/crates/ide_diagnostics/src/handlers/unresolved_proc_macro.rs | |||
@@ -1,7 +1,4 @@ | |||
1 | use crate::{ | 1 | use crate::{Diagnostic, DiagnosticsContext, Severity}; |
2 | diagnostics::{Diagnostic, DiagnosticsContext}, | ||
3 | Severity, | ||
4 | }; | ||
5 | 2 | ||
6 | // Diagnostic: unresolved-proc-macro | 3 | // Diagnostic: unresolved-proc-macro |
7 | // | 4 | // |
@@ -12,7 +9,7 @@ use crate::{ | |||
12 | // If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the | 9 | // If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the |
13 | // `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can | 10 | // `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can |
14 | // enable support for procedural macros (see `rust-analyzer.procMacro.enable`). | 11 | // enable support for procedural macros (see `rust-analyzer.procMacro.enable`). |
15 | pub(super) fn unresolved_proc_macro( | 12 | pub(crate) fn unresolved_proc_macro( |
16 | ctx: &DiagnosticsContext<'_>, | 13 | ctx: &DiagnosticsContext<'_>, |
17 | d: &hir::UnresolvedProcMacro, | 14 | d: &hir::UnresolvedProcMacro, |
18 | ) -> Diagnostic { | 15 | ) -> Diagnostic { |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide_diagnostics/src/lib.rs index 815a633e5..88037be5a 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide_diagnostics/src/lib.rs | |||
@@ -1,34 +1,60 @@ | |||
1 | //! Collects diagnostics & fixits for a single file. | 1 | //! Diagnostics rendering and fixits. |
2 | //! | 2 | //! |
3 | //! The tricky bit here is that diagnostics are produced by hir in terms of | 3 | //! Most of the diagnostics originate from the dark depth of the compiler, and |
4 | //! macro-expanded files, but we need to present them to the users in terms of | 4 | //! are originally expressed in term of IR. When we emit the diagnostic, we are |
5 | //! original files. So we need to map the ranges. | 5 | //! usually not in the position to decide how to best "render" it in terms of |
6 | 6 | //! user-authored source code. We are especially not in the position to offer | |
7 | mod break_outside_of_loop; | 7 | //! fixits, as the compiler completely lacks the infrastructure to edit the |
8 | mod inactive_code; | 8 | //! source code. |
9 | mod incorrect_case; | 9 | //! |
10 | mod macro_error; | 10 | //! Instead, we "bubble up" raw, structured diagnostics until the `hir` crate, |
11 | mod mismatched_arg_count; | 11 | //! where we "cook" them so that each diagnostic is formulated in terms of `hir` |
12 | mod missing_fields; | 12 | //! types. Well, at least that's the aspiration, the "cooking" is somewhat |
13 | mod missing_match_arms; | 13 | //! ad-hoc at the moment. Anyways, we get a bunch of ide-friendly diagnostic |
14 | mod missing_ok_or_some_in_tail_expr; | 14 | //! structs from hir, and we want to render them to unified serializable |
15 | mod missing_unsafe; | 15 | //! representation (span, level, message) here. If we can, we also provide |
16 | mod no_such_field; | 16 | //! fixits. By the way, that's why we want to keep diagnostics structured |
17 | mod remove_this_semicolon; | 17 | //! internally -- so that we have all the info to make fixes. |
18 | mod replace_filter_map_next_with_find_map; | 18 | //! |
19 | mod unimplemented_builtin_macro; | 19 | //! We have one "handler" module per diagnostic code. Such a module contains |
20 | mod unlinked_file; | 20 | //! rendering, optional fixes and tests. It's OK if some low-level compiler |
21 | mod unresolved_extern_crate; | 21 | //! functionality ends up being tested via a diagnostic. |
22 | mod unresolved_import; | 22 | //! |
23 | mod unresolved_macro_call; | 23 | //! There are also a couple of ad-hoc diagnostics implemented directly here, we |
24 | mod unresolved_module; | 24 | //! don't yet have a great pattern for how to do them properly. |
25 | mod unresolved_proc_macro; | 25 | |
26 | mod handlers { | ||
27 | pub(crate) mod break_outside_of_loop; | ||
28 | pub(crate) mod inactive_code; | ||
29 | pub(crate) mod incorrect_case; | ||
30 | pub(crate) mod macro_error; | ||
31 | pub(crate) mod mismatched_arg_count; | ||
32 | pub(crate) mod missing_fields; | ||
33 | pub(crate) mod missing_match_arms; | ||
34 | pub(crate) mod missing_ok_or_some_in_tail_expr; | ||
35 | pub(crate) mod missing_unsafe; | ||
36 | pub(crate) mod no_such_field; | ||
37 | pub(crate) mod remove_this_semicolon; | ||
38 | pub(crate) mod replace_filter_map_next_with_find_map; | ||
39 | pub(crate) mod unimplemented_builtin_macro; | ||
40 | pub(crate) mod unlinked_file; | ||
41 | pub(crate) mod unresolved_extern_crate; | ||
42 | pub(crate) mod unresolved_import; | ||
43 | pub(crate) mod unresolved_macro_call; | ||
44 | pub(crate) mod unresolved_module; | ||
45 | pub(crate) mod unresolved_proc_macro; | ||
46 | } | ||
26 | 47 | ||
27 | mod field_shorthand; | 48 | mod field_shorthand; |
28 | 49 | ||
29 | use hir::{diagnostics::AnyDiagnostic, Semantics}; | 50 | use hir::{diagnostics::AnyDiagnostic, Semantics}; |
30 | use ide_assists::AssistResolveStrategy; | 51 | use ide_db::{ |
31 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 52 | assists::{Assist, AssistId, AssistKind, AssistResolveStrategy}, |
53 | base_db::{FileId, SourceDatabase}, | ||
54 | label::Label, | ||
55 | source_change::SourceChange, | ||
56 | RootDatabase, | ||
57 | }; | ||
32 | use itertools::Itertools; | 58 | use itertools::Itertools; |
33 | use rustc_hash::FxHashSet; | 59 | use rustc_hash::FxHashSet; |
34 | use syntax::{ | 60 | use syntax::{ |
@@ -36,9 +62,8 @@ use syntax::{ | |||
36 | SyntaxNode, TextRange, | 62 | SyntaxNode, TextRange, |
37 | }; | 63 | }; |
38 | use text_edit::TextEdit; | 64 | use text_edit::TextEdit; |
39 | use unlinked_file::UnlinkedFile; | ||
40 | 65 | ||
41 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | 66 | use crate::handlers::unlinked_file::UnlinkedFile; |
42 | 67 | ||
43 | #[derive(Copy, Clone, Debug, PartialEq)] | 68 | #[derive(Copy, Clone, Debug, PartialEq)] |
44 | pub struct DiagnosticCode(pub &'static str); | 69 | pub struct DiagnosticCode(pub &'static str); |
@@ -113,7 +138,7 @@ struct DiagnosticsContext<'a> { | |||
113 | resolve: &'a AssistResolveStrategy, | 138 | resolve: &'a AssistResolveStrategy, |
114 | } | 139 | } |
115 | 140 | ||
116 | pub(crate) fn diagnostics( | 141 | pub fn diagnostics( |
117 | db: &RootDatabase, | 142 | db: &RootDatabase, |
118 | config: &DiagnosticsConfig, | 143 | config: &DiagnosticsConfig, |
119 | resolve: &AssistResolveStrategy, | 144 | resolve: &AssistResolveStrategy, |
@@ -145,32 +170,32 @@ pub(crate) fn diagnostics( | |||
145 | let ctx = DiagnosticsContext { config, sema, resolve }; | 170 | let ctx = DiagnosticsContext { config, sema, resolve }; |
146 | if module.is_none() { | 171 | if module.is_none() { |
147 | let d = UnlinkedFile { file: file_id }; | 172 | let d = UnlinkedFile { file: file_id }; |
148 | let d = unlinked_file::unlinked_file(&ctx, &d); | 173 | let d = handlers::unlinked_file::unlinked_file(&ctx, &d); |
149 | res.push(d) | 174 | res.push(d) |
150 | } | 175 | } |
151 | 176 | ||
152 | for diag in diags { | 177 | for diag in diags { |
153 | #[rustfmt::skip] | 178 | #[rustfmt::skip] |
154 | let d = match diag { | 179 | let d = match diag { |
155 | AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), | 180 | AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), |
156 | AnyDiagnostic::IncorrectCase(d) => incorrect_case::incorrect_case(&ctx, &d), | 181 | AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), |
157 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), | 182 | AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), |
158 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), | 183 | AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d), |
159 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), | 184 | AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d), |
160 | AnyDiagnostic::MissingMatchArms(d) => missing_match_arms::missing_match_arms(&ctx, &d), | 185 | AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d), |
161 | AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), | 186 | AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => handlers::missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), |
162 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), | 187 | AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d), |
163 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), | 188 | AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d), |
164 | AnyDiagnostic::RemoveThisSemicolon(d) => remove_this_semicolon::remove_this_semicolon(&ctx, &d), | 189 | AnyDiagnostic::RemoveThisSemicolon(d) => handlers::remove_this_semicolon::remove_this_semicolon(&ctx, &d), |
165 | AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d), | 190 | AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d), |
166 | AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), | 191 | AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), |
167 | AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), | 192 | AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), |
168 | AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), | 193 | AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d), |
169 | AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), | 194 | AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d), |
170 | AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d), | 195 | AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d), |
171 | AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d), | 196 | AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d), |
172 | 197 | ||
173 | AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) { | 198 | AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) { |
174 | Some(it) => it, | 199 | Some(it) => it, |
175 | None => continue, | 200 | None => continue, |
176 | } | 201 | } |
@@ -261,11 +286,15 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | |||
261 | #[cfg(test)] | 286 | #[cfg(test)] |
262 | mod tests { | 287 | mod tests { |
263 | use expect_test::Expect; | 288 | use expect_test::Expect; |
264 | use ide_assists::AssistResolveStrategy; | 289 | use ide_db::{ |
290 | assists::AssistResolveStrategy, | ||
291 | base_db::{fixture::WithFixture, SourceDatabaseExt}, | ||
292 | RootDatabase, | ||
293 | }; | ||
265 | use stdx::trim_indent; | 294 | use stdx::trim_indent; |
266 | use test_utils::{assert_eq_text, extract_annotations}; | 295 | use test_utils::{assert_eq_text, extract_annotations}; |
267 | 296 | ||
268 | use crate::{fixture, DiagnosticsConfig}; | 297 | use crate::DiagnosticsConfig; |
269 | 298 | ||
270 | /// Takes a multi-file input fixture with annotated cursor positions, | 299 | /// Takes a multi-file input fixture with annotated cursor positions, |
271 | /// and checks that: | 300 | /// and checks that: |
@@ -291,21 +320,20 @@ mod tests { | |||
291 | fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { | 320 | fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { |
292 | let after = trim_indent(ra_fixture_after); | 321 | let after = trim_indent(ra_fixture_after); |
293 | 322 | ||
294 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 323 | let (db, file_position) = RootDatabase::with_position(ra_fixture_before); |
295 | let diagnostic = analysis | 324 | let diagnostic = super::diagnostics( |
296 | .diagnostics( | 325 | &db, |
297 | &DiagnosticsConfig::default(), | 326 | &DiagnosticsConfig::default(), |
298 | AssistResolveStrategy::All, | 327 | &AssistResolveStrategy::All, |
299 | file_position.file_id, | 328 | file_position.file_id, |
300 | ) | 329 | ) |
301 | .unwrap() | 330 | .pop() |
302 | .pop() | 331 | .expect("no diagnostics"); |
303 | .expect("no diagnostics"); | ||
304 | let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; | 332 | let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; |
305 | let actual = { | 333 | let actual = { |
306 | let source_change = fix.source_change.as_ref().unwrap(); | 334 | let source_change = fix.source_change.as_ref().unwrap(); |
307 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | 335 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); |
308 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | 336 | let mut actual = db.file_text(file_id).to_string(); |
309 | 337 | ||
310 | for edit in source_change.source_file_edits.values() { | 338 | for edit in source_change.source_file_edits.values() { |
311 | edit.apply(&mut actual); | 339 | edit.apply(&mut actual); |
@@ -324,24 +352,26 @@ mod tests { | |||
324 | 352 | ||
325 | /// Checks that there's a diagnostic *without* fix at `$0`. | 353 | /// Checks that there's a diagnostic *without* fix at `$0`. |
326 | pub(crate) fn check_no_fix(ra_fixture: &str) { | 354 | pub(crate) fn check_no_fix(ra_fixture: &str) { |
327 | let (analysis, file_position) = fixture::position(ra_fixture); | 355 | let (db, file_position) = RootDatabase::with_position(ra_fixture); |
328 | let diagnostic = analysis | 356 | let diagnostic = super::diagnostics( |
329 | .diagnostics( | 357 | &db, |
330 | &DiagnosticsConfig::default(), | 358 | &DiagnosticsConfig::default(), |
331 | AssistResolveStrategy::All, | 359 | &AssistResolveStrategy::All, |
332 | file_position.file_id, | 360 | file_position.file_id, |
333 | ) | 361 | ) |
334 | .unwrap() | 362 | .pop() |
335 | .pop() | 363 | .unwrap(); |
336 | .unwrap(); | ||
337 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); | 364 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); |
338 | } | 365 | } |
339 | 366 | ||
340 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { | 367 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { |
341 | let (analysis, file_id) = fixture::file(ra_fixture); | 368 | let (db, file_id) = RootDatabase::with_single_file(ra_fixture); |
342 | let diagnostics = analysis | 369 | let diagnostics = super::diagnostics( |
343 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | 370 | &db, |
344 | .unwrap(); | 371 | &DiagnosticsConfig::default(), |
372 | &AssistResolveStrategy::All, | ||
373 | file_id, | ||
374 | ); | ||
345 | expect.assert_debug_eq(&diagnostics) | 375 | expect.assert_debug_eq(&diagnostics) |
346 | } | 376 | } |
347 | 377 | ||
@@ -354,12 +384,12 @@ mod tests { | |||
354 | 384 | ||
355 | #[track_caller] | 385 | #[track_caller] |
356 | pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) { | 386 | pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) { |
357 | let (analysis, files) = fixture::files(ra_fixture); | 387 | let (db, files) = RootDatabase::with_many_files(ra_fixture); |
358 | for file_id in files { | 388 | for file_id in files { |
359 | let diagnostics = | 389 | let diagnostics = |
360 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | 390 | super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id); |
361 | 391 | ||
362 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | 392 | let expected = extract_annotations(&*db.file_text(file_id)); |
363 | let mut actual = | 393 | let mut actual = |
364 | diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); | 394 | diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); |
365 | actual.sort_by_key(|(range, _)| range.start()); | 395 | actual.sort_by_key(|(range, _)| range.start()); |
@@ -455,15 +485,17 @@ mod a { | |||
455 | let mut config = DiagnosticsConfig::default(); | 485 | let mut config = DiagnosticsConfig::default(); |
456 | config.disabled.insert("unresolved-module".into()); | 486 | config.disabled.insert("unresolved-module".into()); |
457 | 487 | ||
458 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 488 | let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#); |
459 | 489 | ||
460 | let diagnostics = | 490 | let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id); |
461 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | ||
462 | assert!(diagnostics.is_empty()); | 491 | assert!(diagnostics.is_empty()); |
463 | 492 | ||
464 | let diagnostics = analysis | 493 | let diagnostics = super::diagnostics( |
465 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | 494 | &db, |
466 | .unwrap(); | 495 | &DiagnosticsConfig::default(), |
496 | &AssistResolveStrategy::All, | ||
497 | file_id, | ||
498 | ); | ||
467 | assert!(!diagnostics.is_empty()); | 499 | assert!(!diagnostics.is_empty()); |
468 | } | 500 | } |
469 | 501 | ||