diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 141 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 24 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 7 |
4 files changed, 169 insertions, 5 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index f5d627b6e..b30cdb6ed 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -96,6 +96,9 @@ pub(crate) fn diagnostics( | |||
96 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 96 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
97 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 97 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
98 | }) | 98 | }) |
99 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | ||
100 | res.borrow_mut().push(warning_with_fix(d, &sema)); | ||
101 | }) | ||
99 | // Only collect experimental diagnostics when they're enabled. | 102 | // Only collect experimental diagnostics when they're enabled. |
100 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | 103 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) |
101 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | 104 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); |
@@ -130,6 +133,15 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabas | |||
130 | } | 133 | } |
131 | } | 134 | } |
132 | 135 | ||
136 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | ||
137 | Diagnostic { | ||
138 | range: sema.diagnostics_display_range(d).range, | ||
139 | message: d.message(), | ||
140 | severity: Severity::WeakWarning, | ||
141 | fix: d.fix(&sema), | ||
142 | } | ||
143 | } | ||
144 | |||
133 | fn check_unnecessary_braces_in_use_statement( | 145 | fn check_unnecessary_braces_in_use_statement( |
134 | acc: &mut Vec<Diagnostic>, | 146 | acc: &mut Vec<Diagnostic>, |
135 | file_id: FileId, | 147 | file_id: FileId, |
@@ -245,8 +257,37 @@ mod tests { | |||
245 | 257 | ||
246 | assert_eq_text!(&after, &actual); | 258 | assert_eq_text!(&after, &actual); |
247 | assert!( | 259 | assert!( |
248 | fix.fix_trigger_range.start() <= file_position.offset | 260 | fix.fix_trigger_range.contains_inclusive(file_position.offset), |
249 | && fix.fix_trigger_range.end() >= file_position.offset, | 261 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
262 | fix.fix_trigger_range, | ||
263 | file_position.offset | ||
264 | ); | ||
265 | } | ||
266 | |||
267 | /// Similar to `check_fix`, but applies all the available fixes. | ||
268 | fn check_fixes(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
269 | let after = trim_indent(ra_fixture_after); | ||
270 | |||
271 | let (analysis, file_position) = fixture::position(ra_fixture_before); | ||
272 | let diagnostic = analysis | ||
273 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | ||
274 | .unwrap() | ||
275 | .pop() | ||
276 | .unwrap(); | ||
277 | let fix = diagnostic.fix.unwrap(); | ||
278 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | ||
279 | let actual = { | ||
280 | let mut actual = target_file_contents.to_string(); | ||
281 | // Go from the last one to the first one, so that ranges won't be affected by previous edits. | ||
282 | for edit in fix.source_change.source_file_edits.iter().rev() { | ||
283 | edit.edit.apply(&mut actual); | ||
284 | } | ||
285 | actual | ||
286 | }; | ||
287 | |||
288 | assert_eq_text!(&after, &actual); | ||
289 | assert!( | ||
290 | fix.fix_trigger_range.contains_inclusive(file_position.offset), | ||
250 | "diagnostic fix range {:?} does not touch cursor position {:?}", | 291 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
251 | fix.fix_trigger_range, | 292 | fix.fix_trigger_range, |
252 | file_position.offset | 293 | file_position.offset |
@@ -790,4 +831,100 @@ struct Foo { | |||
790 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 831 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); |
791 | assert!(!diagnostics.is_empty()); | 832 | assert!(!diagnostics.is_empty()); |
792 | } | 833 | } |
834 | |||
835 | #[test] | ||
836 | fn test_rename_incorrect_case() { | ||
837 | check_fixes( | ||
838 | r#" | ||
839 | pub struct test_struct<|> { one: i32 } | ||
840 | |||
841 | pub fn some_fn(val: test_struct) -> test_struct { | ||
842 | test_struct { one: val.one + 1 } | ||
843 | } | ||
844 | "#, | ||
845 | r#" | ||
846 | pub struct TestStruct { one: i32 } | ||
847 | |||
848 | pub fn some_fn(val: TestStruct) -> TestStruct { | ||
849 | TestStruct { one: val.one + 1 } | ||
850 | } | ||
851 | "#, | ||
852 | ); | ||
853 | |||
854 | check_fixes( | ||
855 | r#" | ||
856 | pub fn some_fn(NonSnakeCase<|>: u8) -> u8 { | ||
857 | NonSnakeCase | ||
858 | } | ||
859 | "#, | ||
860 | r#" | ||
861 | pub fn some_fn(non_snake_case: u8) -> u8 { | ||
862 | non_snake_case | ||
863 | } | ||
864 | "#, | ||
865 | ); | ||
866 | |||
867 | check_fixes( | ||
868 | r#" | ||
869 | pub fn SomeFn<|>(val: u8) -> u8 { | ||
870 | if val != 0 { SomeFn(val - 1) } else { val } | ||
871 | } | ||
872 | "#, | ||
873 | r#" | ||
874 | pub fn some_fn(val: u8) -> u8 { | ||
875 | if val != 0 { some_fn(val - 1) } else { val } | ||
876 | } | ||
877 | "#, | ||
878 | ); | ||
879 | |||
880 | check_fixes( | ||
881 | r#" | ||
882 | fn some_fn() { | ||
883 | let whatAWeird_Formatting<|> = 10; | ||
884 | another_func(whatAWeird_Formatting); | ||
885 | } | ||
886 | "#, | ||
887 | r#" | ||
888 | fn some_fn() { | ||
889 | let what_a_weird_formatting = 10; | ||
890 | another_func(what_a_weird_formatting); | ||
891 | } | ||
892 | "#, | ||
893 | ); | ||
894 | } | ||
895 | |||
896 | #[test] | ||
897 | fn test_uppercase_const_no_diagnostics() { | ||
898 | check_no_diagnostics( | ||
899 | r#" | ||
900 | fn foo() { | ||
901 | const ANOTHER_ITEM<|>: &str = "some_item"; | ||
902 | } | ||
903 | "#, | ||
904 | ); | ||
905 | } | ||
906 | |||
907 | #[test] | ||
908 | fn test_rename_incorrect_case_struct_method() { | ||
909 | check_fixes( | ||
910 | r#" | ||
911 | pub struct TestStruct; | ||
912 | |||
913 | impl TestStruct { | ||
914 | pub fn SomeFn<|>() -> TestStruct { | ||
915 | TestStruct | ||
916 | } | ||
917 | } | ||
918 | "#, | ||
919 | r#" | ||
920 | pub struct TestStruct; | ||
921 | |||
922 | impl TestStruct { | ||
923 | pub fn some_fn() -> TestStruct { | ||
924 | TestStruct | ||
925 | } | ||
926 | } | ||
927 | "#, | ||
928 | ); | ||
929 | } | ||
793 | } | 930 | } |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 68ae1c239..0c75e50b0 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -3,7 +3,10 @@ | |||
3 | use base_db::FileId; | 3 | use base_db::FileId; |
4 | use hir::{ | 4 | use hir::{ |
5 | db::AstDatabase, | 5 | db::AstDatabase, |
6 | diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, | 6 | diagnostics::{ |
7 | Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField, | ||
8 | UnresolvedModule, | ||
9 | }, | ||
7 | HasSource, HirDisplay, Semantics, VariantDef, | 10 | HasSource, HirDisplay, Semantics, VariantDef, |
8 | }; | 11 | }; |
9 | use ide_db::{ | 12 | use ide_db::{ |
@@ -17,7 +20,7 @@ use syntax::{ | |||
17 | }; | 20 | }; |
18 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
19 | 22 | ||
20 | use crate::diagnostics::Fix; | 23 | use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition}; |
21 | 24 | ||
22 | /// A [Diagnostic] that potentially has a fix available. | 25 | /// A [Diagnostic] that potentially has a fix available. |
23 | /// | 26 | /// |
@@ -99,6 +102,23 @@ impl DiagnosticWithFix for MissingOkInTailExpr { | |||
99 | } | 102 | } |
100 | } | 103 | } |
101 | 104 | ||
105 | impl DiagnosticWithFix for IncorrectCase { | ||
106 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
107 | let root = sema.db.parse_or_expand(self.file)?; | ||
108 | let name_node = self.ident.to_node(&root); | ||
109 | |||
110 | let file_id = self.file.original_file(sema.db); | ||
111 | let offset = name_node.syntax().text_range().start(); | ||
112 | let file_position = FilePosition { file_id, offset }; | ||
113 | |||
114 | let rename_changes = | ||
115 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; | ||
116 | |||
117 | let label = format!("Rename to {}", self.suggested_text); | ||
118 | Some(Fix::new(&label, rename_changes.info, rename_changes.range)) | ||
119 | } | ||
120 | } | ||
121 | |||
102 | fn missing_record_expr_field_fix( | 122 | fn missing_record_expr_field_fix( |
103 | sema: &Semantics<RootDatabase>, | 123 | sema: &Semantics<RootDatabase>, |
104 | usage_file_id: FileId, | 124 | usage_file_id: FileId, |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index f65a05ea3..88e2f2db3 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -9,7 +9,7 @@ | |||
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 | mod rename; | 12 | pub(crate) mod rename; |
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::Semantics; |
15 | use ide_db::{ | 15 | use ide_db::{ |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index f3b5cfc8c..f9a11e43d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -42,7 +42,14 @@ pub(crate) fn rename( | |||
42 | new_name: &str, | 42 | new_name: &str, |
43 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 43 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
44 | let sema = Semantics::new(db); | 44 | let sema = Semantics::new(db); |
45 | rename_with_semantics(&sema, position, new_name) | ||
46 | } | ||
45 | 47 | ||
48 | pub(crate) fn rename_with_semantics( | ||
49 | sema: &Semantics<RootDatabase>, | ||
50 | position: FilePosition, | ||
51 | new_name: &str, | ||
52 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | ||
46 | match lex_single_syntax_kind(new_name) { | 53 | match lex_single_syntax_kind(new_name) { |
47 | Some(res) => match res { | 54 | Some(res) => match res { |
48 | (SyntaxKind::IDENT, _) => (), | 55 | (SyntaxKind::IDENT, _) => (), |