aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_diagnostics/src/handlers
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-14 19:23:59 +0100
committerAleksey Kladov <[email protected]>2021-06-14 20:37:06 +0100
commit58712088ac2114e41d754ec6bcb5cd6bc3625256 (patch)
tree396054e4e67546aa158ab2bca0ad2f0f0b9d8fff /crates/ide_diagnostics/src/handlers
parent38ae18b7592f97a7058d97928307bccbd881a582 (diff)
minor: make diagnostics more similar
Diffstat (limited to 'crates/ide_diagnostics/src/handlers')
-rw-r--r--crates/ide_diagnostics/src/handlers/field_shorthand.rs203
-rw-r--r--crates/ide_diagnostics/src/handlers/missing_fields.rs29
-rw-r--r--crates/ide_diagnostics/src/handlers/unlinked_file.rs23
-rw-r--r--crates/ide_diagnostics/src/handlers/useless_braces.rs148
4 files changed, 390 insertions, 13 deletions
diff --git a/crates/ide_diagnostics/src/handlers/field_shorthand.rs b/crates/ide_diagnostics/src/handlers/field_shorthand.rs
new file mode 100644
index 000000000..33152e284
--- /dev/null
+++ b/crates/ide_diagnostics/src/handlers/field_shorthand.rs
@@ -0,0 +1,203 @@
1//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
2//! expressions and patterns.
3
4use ide_db::{base_db::FileId, source_change::SourceChange};
5use syntax::{ast, match_ast, AstNode, SyntaxNode};
6use text_edit::TextEdit;
7
8use crate::{fix, Diagnostic, Severity};
9
10pub(crate) fn field_shorthand(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
11 match_ast! {
12 match node {
13 ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it),
14 ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it),
15 _ => ()
16 }
17 };
18}
19
20fn check_expr_field_shorthand(
21 acc: &mut Vec<Diagnostic>,
22 file_id: FileId,
23 record_expr: ast::RecordExpr,
24) {
25 let record_field_list = match record_expr.record_expr_field_list() {
26 Some(it) => it,
27 None => return,
28 };
29 for record_field in record_field_list.fields() {
30 let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) {
31 Some(it) => it,
32 None => continue,
33 };
34
35 let field_name = name_ref.syntax().text().to_string();
36 let field_expr = expr.syntax().text().to_string();
37 let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
38 if field_name != field_expr || field_name_is_tup_index {
39 continue;
40 }
41
42 let mut edit_builder = TextEdit::builder();
43 edit_builder.delete(record_field.syntax().text_range());
44 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
45 let edit = edit_builder.finish();
46
47 let field_range = record_field.syntax().text_range();
48 acc.push(
49 Diagnostic::new("use-field-shorthand", "Shorthand struct initialization", field_range)
50 .severity(Severity::WeakWarning)
51 .with_fixes(Some(vec![fix(
52 "use_expr_field_shorthand",
53 "Use struct shorthand initialization",
54 SourceChange::from_text_edit(file_id, edit),
55 field_range,
56 )])),
57 );
58 }
59}
60
61fn check_pat_field_shorthand(
62 acc: &mut Vec<Diagnostic>,
63 file_id: FileId,
64 record_pat: ast::RecordPat,
65) {
66 let record_pat_field_list = match record_pat.record_pat_field_list() {
67 Some(it) => it,
68 None => return,
69 };
70 for record_pat_field in record_pat_field_list.fields() {
71 let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) {
72 Some(it) => it,
73 None => continue,
74 };
75
76 let field_name = name_ref.syntax().text().to_string();
77 let field_pat = pat.syntax().text().to_string();
78 let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
79 if field_name != field_pat || field_name_is_tup_index {
80 continue;
81 }
82
83 let mut edit_builder = TextEdit::builder();
84 edit_builder.delete(record_pat_field.syntax().text_range());
85 edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name);
86 let edit = edit_builder.finish();
87
88 let field_range = record_pat_field.syntax().text_range();
89 acc.push(
90 Diagnostic::new("use-field-shorthand", "Shorthand struct pattern", field_range)
91 .severity(Severity::WeakWarning)
92 .with_fixes(Some(vec![fix(
93 "use_pat_field_shorthand",
94 "Use struct field shorthand",
95 SourceChange::from_text_edit(file_id, edit),
96 field_range,
97 )])),
98 );
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use crate::tests::{check_diagnostics, check_fix};
105
106 #[test]
107 fn test_check_expr_field_shorthand() {
108 check_diagnostics(
109 r#"
110struct A { a: &'static str }
111fn main() { A { a: "hello" } }
112"#,
113 );
114 check_diagnostics(
115 r#"
116struct A(usize);
117fn main() { A { 0: 0 } }
118"#,
119 );
120
121 check_fix(
122 r#"
123struct A { a: &'static str }
124fn main() {
125 let a = "haha";
126 A { a$0: a }
127}
128"#,
129 r#"
130struct A { a: &'static str }
131fn main() {
132 let a = "haha";
133 A { a }
134}
135"#,
136 );
137
138 check_fix(
139 r#"
140struct A { a: &'static str, b: &'static str }
141fn main() {
142 let a = "haha";
143 let b = "bb";
144 A { a$0: a, b }
145}
146"#,
147 r#"
148struct A { a: &'static str, b: &'static str }
149fn main() {
150 let a = "haha";
151 let b = "bb";
152 A { a, b }
153}
154"#,
155 );
156 }
157
158 #[test]
159 fn test_check_pat_field_shorthand() {
160 check_diagnostics(
161 r#"
162struct A { a: &'static str }
163fn f(a: A) { let A { a: hello } = a; }
164"#,
165 );
166 check_diagnostics(
167 r#"
168struct A(usize);
169fn f(a: A) { let A { 0: 0 } = a; }
170"#,
171 );
172
173 check_fix(
174 r#"
175struct A { a: &'static str }
176fn f(a: A) {
177 let A { a$0: a } = a;
178}
179"#,
180 r#"
181struct A { a: &'static str }
182fn f(a: A) {
183 let A { a } = a;
184}
185"#,
186 );
187
188 check_fix(
189 r#"
190struct A { a: &'static str, b: &'static str }
191fn f(a: A) {
192 let A { a$0: a, b } = a;
193}
194"#,
195 r#"
196struct A { a: &'static str, b: &'static str }
197fn f(a: A) {
198 let A { a, b } = a;
199}
200"#,
201 );
202 }
203}
diff --git a/crates/ide_diagnostics/src/handlers/missing_fields.rs b/crates/ide_diagnostics/src/handlers/missing_fields.rs
index bc82c0e4a..0dd36fb23 100644
--- a/crates/ide_diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide_diagnostics/src/handlers/missing_fields.rs
@@ -323,4 +323,33 @@ fn f() {
323"#, 323"#,
324 ); 324 );
325 } 325 }
326
327 #[test]
328 fn import_extern_crate_clash_with_inner_item() {
329 // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
330
331 check_diagnostics(
332 r#"
333//- /lib.rs crate:lib deps:jwt
334mod permissions;
335
336use permissions::jwt;
337
338fn f() {
339 fn inner() {}
340 jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
341}
342
343//- /permissions.rs
344pub mod jwt {
345 pub struct Claims {}
346}
347
348//- /jwt/lib.rs crate:jwt
349pub struct Claims {
350 field: u8,
351}
352 "#,
353 );
354 }
326} 355}
diff --git a/crates/ide_diagnostics/src/handlers/unlinked_file.rs b/crates/ide_diagnostics/src/handlers/unlinked_file.rs
index 8921ddde2..8e601fa48 100644
--- a/crates/ide_diagnostics/src/handlers/unlinked_file.rs
+++ b/crates/ide_diagnostics/src/handlers/unlinked_file.rs
@@ -14,32 +14,29 @@ use text_edit::TextEdit;
14 14
15use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; 15use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
16 16
17#[derive(Debug)]
18pub(crate) struct UnlinkedFile {
19 pub(crate) file: FileId,
20}
21
22// Diagnostic: unlinked-file 17// Diagnostic: unlinked-file
23// 18//
24// This diagnostic is shown for files that are not included in any crate, or files that are part of 19// This diagnostic is shown for files that are not included in any crate, or files that are part of
25// crates rust-analyzer failed to discover. The file will not have IDE features available. 20// crates rust-analyzer failed to discover. The file will not have IDE features available.
26pub(crate) fn unlinked_file(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Diagnostic { 21pub(crate) fn unlinked_file(ctx: &DiagnosticsContext, acc: &mut Vec<Diagnostic>, file_id: FileId) {
27 // Limit diagnostic to the first few characters in the file. This matches how VS Code 22 // Limit diagnostic to the first few characters in the file. This matches how VS Code
28 // renders it with the full span, but on other editors, and is less invasive. 23 // renders it with the full span, but on other editors, and is less invasive.
29 let range = ctx.sema.db.parse(d.file).syntax_node().text_range(); 24 let range = ctx.sema.db.parse(file_id).syntax_node().text_range();
30 // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`. 25 // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`.
31 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range); 26 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
32 27
33 Diagnostic::new("unlinked-file", "file not included in module tree", range) 28 acc.push(
34 .with_fixes(fixes(ctx, d)) 29 Diagnostic::new("unlinked-file", "file not included in module tree", range)
30 .with_fixes(fixes(ctx, file_id)),
31 );
35} 32}
36 33
37fn fixes(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Option<Vec<Assist>> { 34fn fixes(ctx: &DiagnosticsContext, file_id: FileId) -> Option<Vec<Assist>> {
38 // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file, 35 // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
39 // suggest that as a fix. 36 // suggest that as a fix.
40 37
41 let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(d.file)); 38 let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(file_id));
42 let our_path = source_root.path_for_file(&d.file)?; 39 let our_path = source_root.path_for_file(&file_id)?;
43 let module_name = our_path.name_and_extension()?.0; 40 let module_name = our_path.name_and_extension()?.0;
44 41
45 // Candidates to look for: 42 // Candidates to look for:
@@ -68,7 +65,7 @@ fn fixes(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Option<Vec<Assist>> {
68 } 65 }
69 66
70 if module.origin.file_id() == Some(*parent_id) { 67 if module.origin.file_id() == Some(*parent_id) {
71 return make_fixes(ctx.sema.db, *parent_id, module_name, d.file); 68 return make_fixes(ctx.sema.db, *parent_id, module_name, file_id);
72 } 69 }
73 } 70 }
74 } 71 }
diff --git a/crates/ide_diagnostics/src/handlers/useless_braces.rs b/crates/ide_diagnostics/src/handlers/useless_braces.rs
new file mode 100644
index 000000000..8b9330e04
--- /dev/null
+++ b/crates/ide_diagnostics/src/handlers/useless_braces.rs
@@ -0,0 +1,148 @@
1use ide_db::{base_db::FileId, source_change::SourceChange};
2use itertools::Itertools;
3use syntax::{ast, AstNode, SyntaxNode, TextRange};
4use text_edit::TextEdit;
5
6use crate::{fix, Diagnostic, Severity};
7
8// Diagnostic: unnecessary-braces
9//
10// Diagnostic for unnecessary braces in `use` items.
11pub(crate) fn useless_braces(
12 acc: &mut Vec<Diagnostic>,
13 file_id: FileId,
14 node: &SyntaxNode,
15) -> Option<()> {
16 let use_tree_list = ast::UseTreeList::cast(node.clone())?;
17 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
18 // If there is a comment inside the bracketed `use`,
19 // assume it is a commented out module path and don't show diagnostic.
20 if use_tree_list.has_inner_comment() {
21 return Some(());
22 }
23
24 let use_range = use_tree_list.syntax().text_range();
25 let edit = remove_braces(&single_use_tree).unwrap_or_else(|| {
26 let to_replace = single_use_tree.syntax().text().to_string();
27 let mut edit_builder = TextEdit::builder();
28 edit_builder.delete(use_range);
29 edit_builder.insert(use_range.start(), to_replace);
30 edit_builder.finish()
31 });
32
33 acc.push(
34 Diagnostic::new(
35 "unnecessary-braces",
36 "Unnecessary braces in use statement".to_string(),
37 use_range,
38 )
39 .severity(Severity::WeakWarning)
40 .with_fixes(Some(vec![fix(
41 "remove_braces",
42 "Remove unnecessary braces",
43 SourceChange::from_text_edit(file_id, edit),
44 use_range,
45 )])),
46 );
47 }
48
49 Some(())
50}
51
52fn remove_braces(single_use_tree: &ast::UseTree) -> Option<TextEdit> {
53 let use_tree_list_node = single_use_tree.syntax().parent()?;
54 if single_use_tree.path()?.segment()?.self_token().is_some() {
55 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
56 let end = use_tree_list_node.text_range().end();
57 return Some(TextEdit::delete(TextRange::new(start, end)));
58 }
59 None
60}
61
62#[cfg(test)]
63mod tests {
64 use crate::tests::{check_diagnostics, check_fix};
65
66 #[test]
67 fn test_check_unnecessary_braces_in_use_statement() {
68 check_diagnostics(
69 r#"
70use a;
71use a::{c, d::e};
72
73mod a {
74 mod c {}
75 mod d {
76 mod e {}
77 }
78}
79"#,
80 );
81 check_diagnostics(
82 r#"
83use a;
84use a::{
85 c,
86 // d::e
87};
88
89mod a {
90 mod c {}
91 mod d {
92 mod e {}
93 }
94}
95"#,
96 );
97 check_fix(
98 r#"
99mod b {}
100use {$0b};
101"#,
102 r#"
103mod b {}
104use b;
105"#,
106 );
107 check_fix(
108 r#"
109mod b {}
110use {b$0};
111"#,
112 r#"
113mod b {}
114use b;
115"#,
116 );
117 check_fix(
118 r#"
119mod a { mod c {} }
120use a::{c$0};
121"#,
122 r#"
123mod a { mod c {} }
124use a::c;
125"#,
126 );
127 check_fix(
128 r#"
129mod a {}
130use a::{self$0};
131"#,
132 r#"
133mod a {}
134use a;
135"#,
136 );
137 check_fix(
138 r#"
139mod a { mod c {} mod d { mod e {} } }
140use a::{c, d::{e$0}};
141"#,
142 r#"
143mod a { mod c {} mod d { mod e {} } }
144use a::{c, d::e};
145"#,
146 );
147 }
148}