aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_diagnostics
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
parent38ae18b7592f97a7058d97928307bccbd881a582 (diff)
minor: make diagnostics more similar
Diffstat (limited to 'crates/ide_diagnostics')
-rw-r--r--crates/ide_diagnostics/src/handlers/field_shorthand.rs (renamed from crates/ide_diagnostics/src/field_shorthand.rs)2
-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
-rw-r--r--crates/ide_diagnostics/src/lib.rs203
5 files changed, 204 insertions, 201 deletions
diff --git a/crates/ide_diagnostics/src/field_shorthand.rs b/crates/ide_diagnostics/src/handlers/field_shorthand.rs
index 0b6af9965..33152e284 100644
--- a/crates/ide_diagnostics/src/field_shorthand.rs
+++ b/crates/ide_diagnostics/src/handlers/field_shorthand.rs
@@ -7,7 +7,7 @@ use text_edit::TextEdit;
7 7
8use crate::{fix, Diagnostic, Severity}; 8use crate::{fix, Diagnostic, Severity};
9 9
10pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { 10pub(crate) fn field_shorthand(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
11 match_ast! { 11 match_ast! {
12 match node { 12 match node {
13 ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it), 13 ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it),
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}
diff --git a/crates/ide_diagnostics/src/lib.rs b/crates/ide_diagnostics/src/lib.rs
index 88037be5a..52474eeab 100644
--- a/crates/ide_diagnostics/src/lib.rs
+++ b/crates/ide_diagnostics/src/lib.rs
@@ -37,15 +37,17 @@ mod handlers {
37 pub(crate) mod remove_this_semicolon; 37 pub(crate) mod remove_this_semicolon;
38 pub(crate) mod replace_filter_map_next_with_find_map; 38 pub(crate) mod replace_filter_map_next_with_find_map;
39 pub(crate) mod unimplemented_builtin_macro; 39 pub(crate) mod unimplemented_builtin_macro;
40 pub(crate) mod unlinked_file;
41 pub(crate) mod unresolved_extern_crate; 40 pub(crate) mod unresolved_extern_crate;
42 pub(crate) mod unresolved_import; 41 pub(crate) mod unresolved_import;
43 pub(crate) mod unresolved_macro_call; 42 pub(crate) mod unresolved_macro_call;
44 pub(crate) mod unresolved_module; 43 pub(crate) mod unresolved_module;
45 pub(crate) mod unresolved_proc_macro; 44 pub(crate) mod unresolved_proc_macro;
46}
47 45
48mod field_shorthand; 46 // The handlers bellow are unusual, the implement the diagnostics as well.
47 pub(crate) mod field_shorthand;
48 pub(crate) mod useless_braces;
49 pub(crate) mod unlinked_file;
50}
49 51
50use hir::{diagnostics::AnyDiagnostic, Semantics}; 52use hir::{diagnostics::AnyDiagnostic, Semantics};
51use ide_db::{ 53use ide_db::{
@@ -55,15 +57,8 @@ use ide_db::{
55 source_change::SourceChange, 57 source_change::SourceChange,
56 RootDatabase, 58 RootDatabase,
57}; 59};
58use itertools::Itertools;
59use rustc_hash::FxHashSet; 60use rustc_hash::FxHashSet;
60use syntax::{ 61use syntax::{ast::AstNode, TextRange};
61 ast::{self, AstNode},
62 SyntaxNode, TextRange,
63};
64use text_edit::TextEdit;
65
66use crate::handlers::unlinked_file::UnlinkedFile;
67 62
68#[derive(Copy, Clone, Debug, PartialEq)] 63#[derive(Copy, Clone, Debug, PartialEq)]
69pub struct DiagnosticCode(pub &'static str); 64pub struct DiagnosticCode(pub &'static str);
@@ -123,6 +118,8 @@ impl Diagnostic {
123#[derive(Debug, Copy, Clone)] 118#[derive(Debug, Copy, Clone)]
124pub enum Severity { 119pub enum Severity {
125 Error, 120 Error,
121 // We don't actually emit this one yet, but we should at some point.
122 // Warning,
126 WeakWarning, 123 WeakWarning,
127} 124}
128 125
@@ -157,21 +154,20 @@ pub fn diagnostics(
157 ); 154 );
158 155
159 for node in parse.tree().syntax().descendants() { 156 for node in parse.tree().syntax().descendants() {
160 check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); 157 handlers::useless_braces::useless_braces(&mut res, file_id, &node);
161 field_shorthand::check(&mut res, file_id, &node); 158 handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
162 } 159 }
163 160
164 let mut diags = Vec::new();
165 let module = sema.to_module_def(file_id); 161 let module = sema.to_module_def(file_id);
166 if let Some(m) = module {
167 m.diagnostics(db, &mut diags)
168 }
169 162
170 let ctx = DiagnosticsContext { config, sema, resolve }; 163 let ctx = DiagnosticsContext { config, sema, resolve };
171 if module.is_none() { 164 if module.is_none() {
172 let d = UnlinkedFile { file: file_id }; 165 handlers::unlinked_file::unlinked_file(&ctx, &mut res, file_id);
173 let d = handlers::unlinked_file::unlinked_file(&ctx, &d); 166 }
174 res.push(d) 167
168 let mut diags = Vec::new();
169 if let Some(m) = module {
170 m.diagnostics(db, &mut diags)
175 } 171 }
176 172
177 for diag in diags { 173 for diag in diags {
@@ -211,61 +207,6 @@ pub fn diagnostics(
211 res 207 res
212} 208}
213 209
214fn check_unnecessary_braces_in_use_statement(
215 acc: &mut Vec<Diagnostic>,
216 file_id: FileId,
217 node: &SyntaxNode,
218) -> Option<()> {
219 let use_tree_list = ast::UseTreeList::cast(node.clone())?;
220 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
221 // If there is a comment inside the bracketed `use`,
222 // assume it is a commented out module path and don't show diagnostic.
223 if use_tree_list.has_inner_comment() {
224 return Some(());
225 }
226
227 let use_range = use_tree_list.syntax().text_range();
228 let edit =
229 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
230 .unwrap_or_else(|| {
231 let to_replace = single_use_tree.syntax().text().to_string();
232 let mut edit_builder = TextEdit::builder();
233 edit_builder.delete(use_range);
234 edit_builder.insert(use_range.start(), to_replace);
235 edit_builder.finish()
236 });
237
238 acc.push(
239 Diagnostic::new(
240 "unnecessary-braces",
241 "Unnecessary braces in use statement".to_string(),
242 use_range,
243 )
244 .severity(Severity::WeakWarning)
245 .with_fixes(Some(vec![fix(
246 "remove_braces",
247 "Remove unnecessary braces",
248 SourceChange::from_text_edit(file_id, edit),
249 use_range,
250 )])),
251 );
252 }
253
254 Some(())
255}
256
257fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
258 single_use_tree: &ast::UseTree,
259) -> Option<TextEdit> {
260 let use_tree_list_node = single_use_tree.syntax().parent()?;
261 if single_use_tree.path()?.segment()?.self_token().is_some() {
262 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
263 let end = use_tree_list_node.text_range().end();
264 return Some(TextEdit::delete(TextRange::new(start, end)));
265 }
266 None
267}
268
269fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { 210fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
270 let mut res = unresolved_fix(id, label, target); 211 let mut res = unresolved_fix(id, label, target);
271 res.source_change = Some(source_change); 212 res.source_change = Some(source_change);
@@ -398,89 +339,6 @@ mod tests {
398 } 339 }
399 340
400 #[test] 341 #[test]
401 fn test_check_unnecessary_braces_in_use_statement() {
402 check_diagnostics(
403 r#"
404use a;
405use a::{c, d::e};
406
407mod a {
408 mod c {}
409 mod d {
410 mod e {}
411 }
412}
413"#,
414 );
415 check_diagnostics(
416 r#"
417use a;
418use a::{
419 c,
420 // d::e
421};
422
423mod a {
424 mod c {}
425 mod d {
426 mod e {}
427 }
428}
429"#,
430 );
431 check_fix(
432 r"
433 mod b {}
434 use {$0b};
435 ",
436 r"
437 mod b {}
438 use b;
439 ",
440 );
441 check_fix(
442 r"
443 mod b {}
444 use {b$0};
445 ",
446 r"
447 mod b {}
448 use b;
449 ",
450 );
451 check_fix(
452 r"
453 mod a { mod c {} }
454 use a::{c$0};
455 ",
456 r"
457 mod a { mod c {} }
458 use a::c;
459 ",
460 );
461 check_fix(
462 r"
463 mod a {}
464 use a::{self$0};
465 ",
466 r"
467 mod a {}
468 use a;
469 ",
470 );
471 check_fix(
472 r"
473 mod a { mod c {} mod d { mod e {} } }
474 use a::{c, d::{e$0}};
475 ",
476 r"
477 mod a { mod c {} mod d { mod e {} } }
478 use a::{c, d::e};
479 ",
480 );
481 }
482
483 #[test]
484 fn test_disabled_diagnostics() { 342 fn test_disabled_diagnostics() {
485 let mut config = DiagnosticsConfig::default(); 343 let mut config = DiagnosticsConfig::default();
486 config.disabled.insert("unresolved-module".into()); 344 config.disabled.insert("unresolved-module".into());
@@ -498,33 +356,4 @@ mod a {
498 ); 356 );
499 assert!(!diagnostics.is_empty()); 357 assert!(!diagnostics.is_empty());
500 } 358 }
501
502 #[test]
503 fn import_extern_crate_clash_with_inner_item() {
504 // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
505
506 check_diagnostics(
507 r#"
508//- /lib.rs crate:lib deps:jwt
509mod permissions;
510
511use permissions::jwt;
512
513fn f() {
514 fn inner() {}
515 jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
516}
517
518//- /permissions.rs
519pub mod jwt {
520 pub struct Claims {}
521}
522
523//- /jwt/lib.rs crate:jwt
524pub struct Claims {
525 field: u8,
526}
527 "#,
528 );
529 }
530} 359}