aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/diagnostics.rs141
-rw-r--r--crates/ide/src/diagnostics/fixes.rs24
-rw-r--r--crates/ide/src/references.rs2
-rw-r--r--crates/ide/src/references/rename.rs7
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
136fn 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
133fn check_unnecessary_braces_in_use_statement( 145fn 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#"
839pub struct test_struct<|> { one: i32 }
840
841pub fn some_fn(val: test_struct) -> test_struct {
842 test_struct { one: val.one + 1 }
843}
844"#,
845 r#"
846pub struct TestStruct { one: i32 }
847
848pub fn some_fn(val: TestStruct) -> TestStruct {
849 TestStruct { one: val.one + 1 }
850}
851"#,
852 );
853
854 check_fixes(
855 r#"
856pub fn some_fn(NonSnakeCase<|>: u8) -> u8 {
857 NonSnakeCase
858}
859"#,
860 r#"
861pub fn some_fn(non_snake_case: u8) -> u8 {
862 non_snake_case
863}
864"#,
865 );
866
867 check_fixes(
868 r#"
869pub fn SomeFn<|>(val: u8) -> u8 {
870 if val != 0 { SomeFn(val - 1) } else { val }
871}
872"#,
873 r#"
874pub 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#"
882fn some_fn() {
883 let whatAWeird_Formatting<|> = 10;
884 another_func(whatAWeird_Formatting);
885}
886"#,
887 r#"
888fn 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#"
900fn 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#"
911pub struct TestStruct;
912
913impl TestStruct {
914 pub fn SomeFn<|>() -> TestStruct {
915 TestStruct
916 }
917}
918"#,
919 r#"
920pub struct TestStruct;
921
922impl 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 @@
3use base_db::FileId; 3use base_db::FileId;
4use hir::{ 4use 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};
9use ide_db::{ 12use ide_db::{
@@ -17,7 +20,7 @@ use syntax::{
17}; 20};
18use text_edit::TextEdit; 21use text_edit::TextEdit;
19 22
20use crate::diagnostics::Fix; 23use 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
105impl 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
102fn missing_record_expr_field_fix( 122fn 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
12mod rename; 12pub(crate) mod rename;
13 13
14use hir::Semantics; 14use hir::Semantics;
15use ide_db::{ 15use 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
48pub(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, _) => (),