diff options
author | Aleksey Kladov <[email protected]> | 2021-06-14 11:15:05 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-06-14 15:45:17 +0100 |
commit | 1d2772c2c7dc0a42d8a9429d24ea41412add61b3 (patch) | |
tree | 2e727c6465f972b7f62857bc1143e08f4b4416d4 | |
parent | 3d2f0400a26ef6b07d61a06e1b543072b627570e (diff) |
internal: move diagnostics to a new crate
-rw-r--r-- | Cargo.lock | 17 | ||||
-rw-r--r-- | crates/base_db/src/fixture.rs | 8 | ||||
-rw-r--r-- | crates/ide/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 498 | ||||
-rw-r--r-- | crates/ide/src/fixture.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/Cargo.toml | 18 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/break_outside_of_loop.rs (renamed from crates/ide/src/diagnostics/break_outside_of_loop.rs) | 4 | ||||
-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/inactive_code.rs (renamed from crates/ide/src/diagnostics/inactive_code.rs) | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/incorrect_case.rs (renamed from crates/ide/src/diagnostics/incorrect_case.rs) | 26 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/lib.rs | 510 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/macro_error.rs (renamed from crates/ide/src/diagnostics/macro_error.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/mismatched_arg_count.rs (renamed from crates/ide/src/diagnostics/mismatched_arg_count.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/missing_fields.rs (renamed from crates/ide/src/diagnostics/missing_fields.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/missing_match_arms.rs (renamed from crates/ide/src/diagnostics/missing_match_arms.rs) | 6 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/missing_ok_or_some_in_tail_expr.rs (renamed from crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/missing_unsafe.rs (renamed from crates/ide/src/diagnostics/missing_unsafe.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/no_such_field.rs (renamed from crates/ide/src/diagnostics/no_such_field.rs) | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/remove_this_semicolon.rs (renamed from crates/ide/src/diagnostics/remove_this_semicolon.rs) | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/replace_filter_map_next_with_find_map.rs (renamed from crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs) | 9 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unimplemented_builtin_macro.rs (renamed from crates/ide/src/diagnostics/unimplemented_builtin_macro.rs) | 5 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unlinked_file.rs (renamed from crates/ide/src/diagnostics/unlinked_file.rs) | 7 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unresolved_extern_crate.rs (renamed from crates/ide/src/diagnostics/unresolved_extern_crate.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unresolved_import.rs (renamed from crates/ide/src/diagnostics/unresolved_import.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unresolved_macro_call.rs (renamed from crates/ide/src/diagnostics/unresolved_macro_call.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unresolved_module.rs (renamed from crates/ide/src/diagnostics/unresolved_module.rs) | 4 | ||||
-rw-r--r-- | crates/ide_diagnostics/src/unresolved_proc_macro.rs (renamed from crates/ide/src/diagnostics/unresolved_proc_macro.rs) | 5 |
28 files changed, 612 insertions, 578 deletions
diff --git a/Cargo.lock b/Cargo.lock index 04c235341..55016ccf7 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -591,6 +591,7 @@ dependencies = [ | |||
591 | "ide_assists", | 591 | "ide_assists", |
592 | "ide_completion", | 592 | "ide_completion", |
593 | "ide_db", | 593 | "ide_db", |
594 | "ide_diagnostics", | ||
594 | "ide_ssr", | 595 | "ide_ssr", |
595 | "indexmap", | 596 | "indexmap", |
596 | "itertools", | 597 | "itertools", |
@@ -671,6 +672,22 @@ dependencies = [ | |||
671 | [[package]] | 672 | [[package]] |
672 | name = "ide_diagnostics" | 673 | name = "ide_diagnostics" |
673 | version = "0.0.0" | 674 | version = "0.0.0" |
675 | dependencies = [ | ||
676 | "cfg", | ||
677 | "cov-mark", | ||
678 | "either", | ||
679 | "expect-test", | ||
680 | "hir", | ||
681 | "ide_assists", | ||
682 | "ide_db", | ||
683 | "itertools", | ||
684 | "profile", | ||
685 | "rustc-hash", | ||
686 | "stdx", | ||
687 | "syntax", | ||
688 | "test_utils", | ||
689 | "text_edit", | ||
690 | ] | ||
674 | 691 | ||
675 | [[package]] | 692 | [[package]] |
676 | name = "ide_ssr" | 693 | name = "ide_ssr" |
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/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/diagnostics.rs b/crates/ide/src/diagnostics.rs deleted file mode 100644 index 815a633e5..000000000 --- a/crates/ide/src/diagnostics.rs +++ /dev/null | |||
@@ -1,498 +0,0 @@ | |||
1 | //! Collects diagnostics & fixits for a single file. | ||
2 | //! | ||
3 | //! The tricky bit here is that diagnostics are produced by hir in terms of | ||
4 | //! macro-expanded files, but we need to present them to the users in terms of | ||
5 | //! original files. So we need to map the ranges. | ||
6 | |||
7 | mod break_outside_of_loop; | ||
8 | mod inactive_code; | ||
9 | mod incorrect_case; | ||
10 | mod macro_error; | ||
11 | mod mismatched_arg_count; | ||
12 | mod missing_fields; | ||
13 | mod missing_match_arms; | ||
14 | mod missing_ok_or_some_in_tail_expr; | ||
15 | mod missing_unsafe; | ||
16 | mod no_such_field; | ||
17 | mod remove_this_semicolon; | ||
18 | mod replace_filter_map_next_with_find_map; | ||
19 | mod unimplemented_builtin_macro; | ||
20 | mod unlinked_file; | ||
21 | mod unresolved_extern_crate; | ||
22 | mod unresolved_import; | ||
23 | mod unresolved_macro_call; | ||
24 | mod unresolved_module; | ||
25 | mod unresolved_proc_macro; | ||
26 | |||
27 | mod field_shorthand; | ||
28 | |||
29 | use hir::{diagnostics::AnyDiagnostic, Semantics}; | ||
30 | use ide_assists::AssistResolveStrategy; | ||
31 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | ||
32 | use itertools::Itertools; | ||
33 | use rustc_hash::FxHashSet; | ||
34 | use syntax::{ | ||
35 | ast::{self, AstNode}, | ||
36 | SyntaxNode, TextRange, | ||
37 | }; | ||
38 | use text_edit::TextEdit; | ||
39 | use unlinked_file::UnlinkedFile; | ||
40 | |||
41 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | ||
42 | |||
43 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
44 | pub struct DiagnosticCode(pub &'static str); | ||
45 | |||
46 | impl DiagnosticCode { | ||
47 | pub fn as_str(&self) -> &str { | ||
48 | self.0 | ||
49 | } | ||
50 | } | ||
51 | |||
52 | #[derive(Debug)] | ||
53 | pub struct Diagnostic { | ||
54 | pub code: DiagnosticCode, | ||
55 | pub message: String, | ||
56 | pub range: TextRange, | ||
57 | pub severity: Severity, | ||
58 | pub unused: bool, | ||
59 | pub experimental: bool, | ||
60 | pub fixes: Option<Vec<Assist>>, | ||
61 | } | ||
62 | |||
63 | impl Diagnostic { | ||
64 | fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic { | ||
65 | let message = message.into(); | ||
66 | Diagnostic { | ||
67 | code: DiagnosticCode(code), | ||
68 | message, | ||
69 | range, | ||
70 | severity: Severity::Error, | ||
71 | unused: false, | ||
72 | experimental: false, | ||
73 | fixes: None, | ||
74 | } | ||
75 | } | ||
76 | |||
77 | fn experimental(mut self) -> Diagnostic { | ||
78 | self.experimental = true; | ||
79 | self | ||
80 | } | ||
81 | |||
82 | fn severity(mut self, severity: Severity) -> Diagnostic { | ||
83 | self.severity = severity; | ||
84 | self | ||
85 | } | ||
86 | |||
87 | fn with_fixes(mut self, fixes: Option<Vec<Assist>>) -> Diagnostic { | ||
88 | self.fixes = fixes; | ||
89 | self | ||
90 | } | ||
91 | |||
92 | fn with_unused(mut self, unused: bool) -> Diagnostic { | ||
93 | self.unused = unused; | ||
94 | self | ||
95 | } | ||
96 | } | ||
97 | |||
98 | #[derive(Debug, Copy, Clone)] | ||
99 | pub enum Severity { | ||
100 | Error, | ||
101 | WeakWarning, | ||
102 | } | ||
103 | |||
104 | #[derive(Default, Debug, Clone)] | ||
105 | pub struct DiagnosticsConfig { | ||
106 | pub disable_experimental: bool, | ||
107 | pub disabled: FxHashSet<String>, | ||
108 | } | ||
109 | |||
110 | struct DiagnosticsContext<'a> { | ||
111 | config: &'a DiagnosticsConfig, | ||
112 | sema: Semantics<'a, RootDatabase>, | ||
113 | resolve: &'a AssistResolveStrategy, | ||
114 | } | ||
115 | |||
116 | pub(crate) fn diagnostics( | ||
117 | db: &RootDatabase, | ||
118 | config: &DiagnosticsConfig, | ||
119 | resolve: &AssistResolveStrategy, | ||
120 | file_id: FileId, | ||
121 | ) -> Vec<Diagnostic> { | ||
122 | let _p = profile::span("diagnostics"); | ||
123 | let sema = Semantics::new(db); | ||
124 | let parse = db.parse(file_id); | ||
125 | let mut res = Vec::new(); | ||
126 | |||
127 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. | ||
128 | res.extend( | ||
129 | parse.errors().iter().take(128).map(|err| { | ||
130 | Diagnostic::new("syntax-error", format!("Syntax Error: {}", err), err.range()) | ||
131 | }), | ||
132 | ); | ||
133 | |||
134 | for node in parse.tree().syntax().descendants() { | ||
135 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | ||
136 | field_shorthand::check(&mut res, file_id, &node); | ||
137 | } | ||
138 | |||
139 | let mut diags = Vec::new(); | ||
140 | let module = sema.to_module_def(file_id); | ||
141 | if let Some(m) = module { | ||
142 | m.diagnostics(db, &mut diags) | ||
143 | } | ||
144 | |||
145 | let ctx = DiagnosticsContext { config, sema, resolve }; | ||
146 | if module.is_none() { | ||
147 | let d = UnlinkedFile { file: file_id }; | ||
148 | let d = unlinked_file::unlinked_file(&ctx, &d); | ||
149 | res.push(d) | ||
150 | } | ||
151 | |||
152 | for diag in diags { | ||
153 | #[rustfmt::skip] | ||
154 | let d = match diag { | ||
155 | AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), | ||
156 | AnyDiagnostic::IncorrectCase(d) => incorrect_case::incorrect_case(&ctx, &d), | ||
157 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), | ||
158 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), | ||
159 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), | ||
160 | AnyDiagnostic::MissingMatchArms(d) => 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), | ||
162 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), | ||
163 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), | ||
164 | AnyDiagnostic::RemoveThisSemicolon(d) => 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), | ||
166 | AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), | ||
167 | AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), | ||
168 | AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), | ||
169 | AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), | ||
170 | AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d), | ||
171 | AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d), | ||
172 | |||
173 | AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) { | ||
174 | Some(it) => it, | ||
175 | None => continue, | ||
176 | } | ||
177 | }; | ||
178 | res.push(d) | ||
179 | } | ||
180 | |||
181 | res.retain(|d| { | ||
182 | !ctx.config.disabled.contains(d.code.as_str()) | ||
183 | && !(ctx.config.disable_experimental && d.experimental) | ||
184 | }); | ||
185 | |||
186 | res | ||
187 | } | ||
188 | |||
189 | fn check_unnecessary_braces_in_use_statement( | ||
190 | acc: &mut Vec<Diagnostic>, | ||
191 | file_id: FileId, | ||
192 | node: &SyntaxNode, | ||
193 | ) -> Option<()> { | ||
194 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | ||
195 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
196 | // If there is a comment inside the bracketed `use`, | ||
197 | // assume it is a commented out module path and don't show diagnostic. | ||
198 | if use_tree_list.has_inner_comment() { | ||
199 | return Some(()); | ||
200 | } | ||
201 | |||
202 | let use_range = use_tree_list.syntax().text_range(); | ||
203 | let edit = | ||
204 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | ||
205 | .unwrap_or_else(|| { | ||
206 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
207 | let mut edit_builder = TextEdit::builder(); | ||
208 | edit_builder.delete(use_range); | ||
209 | edit_builder.insert(use_range.start(), to_replace); | ||
210 | edit_builder.finish() | ||
211 | }); | ||
212 | |||
213 | acc.push( | ||
214 | Diagnostic::new( | ||
215 | "unnecessary-braces", | ||
216 | "Unnecessary braces in use statement".to_string(), | ||
217 | use_range, | ||
218 | ) | ||
219 | .severity(Severity::WeakWarning) | ||
220 | .with_fixes(Some(vec![fix( | ||
221 | "remove_braces", | ||
222 | "Remove unnecessary braces", | ||
223 | SourceChange::from_text_edit(file_id, edit), | ||
224 | use_range, | ||
225 | )])), | ||
226 | ); | ||
227 | } | ||
228 | |||
229 | Some(()) | ||
230 | } | ||
231 | |||
232 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
233 | single_use_tree: &ast::UseTree, | ||
234 | ) -> Option<TextEdit> { | ||
235 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
236 | if single_use_tree.path()?.segment()?.self_token().is_some() { | ||
237 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | ||
238 | let end = use_tree_list_node.text_range().end(); | ||
239 | return Some(TextEdit::delete(TextRange::new(start, end))); | ||
240 | } | ||
241 | None | ||
242 | } | ||
243 | |||
244 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | ||
245 | let mut res = unresolved_fix(id, label, target); | ||
246 | res.source_change = Some(source_change); | ||
247 | res | ||
248 | } | ||
249 | |||
250 | fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | ||
251 | assert!(!id.contains(' ')); | ||
252 | Assist { | ||
253 | id: AssistId(id, AssistKind::QuickFix), | ||
254 | label: Label::new(label), | ||
255 | group: None, | ||
256 | target, | ||
257 | source_change: None, | ||
258 | } | ||
259 | } | ||
260 | |||
261 | #[cfg(test)] | ||
262 | mod tests { | ||
263 | use expect_test::Expect; | ||
264 | use ide_assists::AssistResolveStrategy; | ||
265 | use stdx::trim_indent; | ||
266 | use test_utils::{assert_eq_text, extract_annotations}; | ||
267 | |||
268 | use crate::{fixture, DiagnosticsConfig}; | ||
269 | |||
270 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
271 | /// and checks that: | ||
272 | /// * a diagnostic is produced | ||
273 | /// * the first diagnostic fix trigger range touches the input cursor position | ||
274 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | ||
275 | #[track_caller] | ||
276 | pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
277 | check_nth_fix(0, ra_fixture_before, ra_fixture_after); | ||
278 | } | ||
279 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
280 | /// and checks that: | ||
281 | /// * a diagnostic is produced | ||
282 | /// * every diagnostic fixes trigger range touches the input cursor position | ||
283 | /// * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied | ||
284 | pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) { | ||
285 | for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() { | ||
286 | check_nth_fix(i, ra_fixture_before, ra_fixture_after) | ||
287 | } | ||
288 | } | ||
289 | |||
290 | #[track_caller] | ||
291 | fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { | ||
292 | let after = trim_indent(ra_fixture_after); | ||
293 | |||
294 | let (analysis, file_position) = fixture::position(ra_fixture_before); | ||
295 | let diagnostic = analysis | ||
296 | .diagnostics( | ||
297 | &DiagnosticsConfig::default(), | ||
298 | AssistResolveStrategy::All, | ||
299 | file_position.file_id, | ||
300 | ) | ||
301 | .unwrap() | ||
302 | .pop() | ||
303 | .expect("no diagnostics"); | ||
304 | let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; | ||
305 | let actual = { | ||
306 | let source_change = fix.source_change.as_ref().unwrap(); | ||
307 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | ||
308 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | ||
309 | |||
310 | for edit in source_change.source_file_edits.values() { | ||
311 | edit.apply(&mut actual); | ||
312 | } | ||
313 | actual | ||
314 | }; | ||
315 | |||
316 | assert_eq_text!(&after, &actual); | ||
317 | assert!( | ||
318 | fix.target.contains_inclusive(file_position.offset), | ||
319 | "diagnostic fix range {:?} does not touch cursor position {:?}", | ||
320 | fix.target, | ||
321 | file_position.offset | ||
322 | ); | ||
323 | } | ||
324 | |||
325 | /// Checks that there's a diagnostic *without* fix at `$0`. | ||
326 | pub(crate) fn check_no_fix(ra_fixture: &str) { | ||
327 | let (analysis, file_position) = fixture::position(ra_fixture); | ||
328 | let diagnostic = analysis | ||
329 | .diagnostics( | ||
330 | &DiagnosticsConfig::default(), | ||
331 | AssistResolveStrategy::All, | ||
332 | file_position.file_id, | ||
333 | ) | ||
334 | .unwrap() | ||
335 | .pop() | ||
336 | .unwrap(); | ||
337 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); | ||
338 | } | ||
339 | |||
340 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { | ||
341 | let (analysis, file_id) = fixture::file(ra_fixture); | ||
342 | let diagnostics = analysis | ||
343 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
344 | .unwrap(); | ||
345 | expect.assert_debug_eq(&diagnostics) | ||
346 | } | ||
347 | |||
348 | #[track_caller] | ||
349 | pub(crate) fn check_diagnostics(ra_fixture: &str) { | ||
350 | let mut config = DiagnosticsConfig::default(); | ||
351 | config.disabled.insert("inactive-code".to_string()); | ||
352 | check_diagnostics_with_config(config, ra_fixture) | ||
353 | } | ||
354 | |||
355 | #[track_caller] | ||
356 | pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) { | ||
357 | let (analysis, files) = fixture::files(ra_fixture); | ||
358 | for file_id in files { | ||
359 | let diagnostics = | ||
360 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | ||
361 | |||
362 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | ||
363 | let mut actual = | ||
364 | diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); | ||
365 | actual.sort_by_key(|(range, _)| range.start()); | ||
366 | assert_eq!(expected, actual); | ||
367 | } | ||
368 | } | ||
369 | |||
370 | #[test] | ||
371 | fn test_check_unnecessary_braces_in_use_statement() { | ||
372 | check_diagnostics( | ||
373 | r#" | ||
374 | use a; | ||
375 | use a::{c, d::e}; | ||
376 | |||
377 | mod a { | ||
378 | mod c {} | ||
379 | mod d { | ||
380 | mod e {} | ||
381 | } | ||
382 | } | ||
383 | "#, | ||
384 | ); | ||
385 | check_diagnostics( | ||
386 | r#" | ||
387 | use a; | ||
388 | use a::{ | ||
389 | c, | ||
390 | // d::e | ||
391 | }; | ||
392 | |||
393 | mod a { | ||
394 | mod c {} | ||
395 | mod d { | ||
396 | mod e {} | ||
397 | } | ||
398 | } | ||
399 | "#, | ||
400 | ); | ||
401 | check_fix( | ||
402 | r" | ||
403 | mod b {} | ||
404 | use {$0b}; | ||
405 | ", | ||
406 | r" | ||
407 | mod b {} | ||
408 | use b; | ||
409 | ", | ||
410 | ); | ||
411 | check_fix( | ||
412 | r" | ||
413 | mod b {} | ||
414 | use {b$0}; | ||
415 | ", | ||
416 | r" | ||
417 | mod b {} | ||
418 | use b; | ||
419 | ", | ||
420 | ); | ||
421 | check_fix( | ||
422 | r" | ||
423 | mod a { mod c {} } | ||
424 | use a::{c$0}; | ||
425 | ", | ||
426 | r" | ||
427 | mod a { mod c {} } | ||
428 | use a::c; | ||
429 | ", | ||
430 | ); | ||
431 | check_fix( | ||
432 | r" | ||
433 | mod a {} | ||
434 | use a::{self$0}; | ||
435 | ", | ||
436 | r" | ||
437 | mod a {} | ||
438 | use a; | ||
439 | ", | ||
440 | ); | ||
441 | check_fix( | ||
442 | r" | ||
443 | mod a { mod c {} mod d { mod e {} } } | ||
444 | use a::{c, d::{e$0}}; | ||
445 | ", | ||
446 | r" | ||
447 | mod a { mod c {} mod d { mod e {} } } | ||
448 | use a::{c, d::e}; | ||
449 | ", | ||
450 | ); | ||
451 | } | ||
452 | |||
453 | #[test] | ||
454 | fn test_disabled_diagnostics() { | ||
455 | let mut config = DiagnosticsConfig::default(); | ||
456 | config.disabled.insert("unresolved-module".into()); | ||
457 | |||
458 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | ||
459 | |||
460 | let diagnostics = | ||
461 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | ||
462 | assert!(diagnostics.is_empty()); | ||
463 | |||
464 | let diagnostics = analysis | ||
465 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
466 | .unwrap(); | ||
467 | assert!(!diagnostics.is_empty()); | ||
468 | } | ||
469 | |||
470 | #[test] | ||
471 | fn import_extern_crate_clash_with_inner_item() { | ||
472 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. | ||
473 | |||
474 | check_diagnostics( | ||
475 | r#" | ||
476 | //- /lib.rs crate:lib deps:jwt | ||
477 | mod permissions; | ||
478 | |||
479 | use permissions::jwt; | ||
480 | |||
481 | fn f() { | ||
482 | fn inner() {} | ||
483 | jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic | ||
484 | } | ||
485 | |||
486 | //- /permissions.rs | ||
487 | pub mod jwt { | ||
488 | pub struct Claims {} | ||
489 | } | ||
490 | |||
491 | //- /jwt/lib.rs crate:jwt | ||
492 | pub struct Claims { | ||
493 | field: u8, | ||
494 | } | ||
495 | "#, | ||
496 | ); | ||
497 | } | ||
498 | } | ||
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..0019b7ba5 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; |
@@ -71,7 +70,6 @@ use crate::display::ToNav; | |||
71 | pub use crate::{ | 70 | pub use crate::{ |
72 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, | 71 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, |
73 | call_hierarchy::CallItem, | 72 | call_hierarchy::CallItem, |
74 | diagnostics::{Diagnostic, DiagnosticsConfig, Severity}, | ||
75 | display::navigation_target::NavigationTarget, | 73 | display::navigation_target::NavigationTarget, |
76 | expand_macro::ExpandedMacro, | 74 | expand_macro::ExpandedMacro, |
77 | file_structure::{StructureNode, StructureNodeKind}, | 75 | file_structure::{StructureNode, StructureNodeKind}, |
@@ -109,6 +107,7 @@ pub use ide_db::{ | |||
109 | symbol_index::Query, | 107 | symbol_index::Query, |
110 | RootDatabase, SymbolKind, | 108 | RootDatabase, SymbolKind, |
111 | }; | 109 | }; |
110 | pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, Severity}; | ||
112 | pub use ide_ssr::SsrError; | 111 | pub use ide_ssr::SsrError; |
113 | pub use syntax::{TextRange, TextSize}; | 112 | pub use syntax::{TextRange, TextSize}; |
114 | pub use text_edit::{Indel, TextEdit}; | 113 | pub use text_edit::{Indel, TextEdit}; |
@@ -549,7 +548,7 @@ impl Analysis { | |||
549 | resolve: AssistResolveStrategy, | 548 | resolve: AssistResolveStrategy, |
550 | file_id: FileId, | 549 | file_id: FileId, |
551 | ) -> Cancellable<Vec<Diagnostic>> { | 550 | ) -> Cancellable<Vec<Diagnostic>> { |
552 | self.with_db(|db| diagnostics::diagnostics(db, config, &resolve, file_id)) | 551 | self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id)) |
553 | } | 552 | } |
554 | 553 | ||
555 | /// Convenience function to return assists + quick fixes for diagnostics | 554 | /// Convenience function to return assists + quick fixes for diagnostics |
@@ -568,7 +567,7 @@ impl Analysis { | |||
568 | self.with_db(|db| { | 567 | self.with_db(|db| { |
569 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | 568 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); |
570 | let diagnostic_assists = if include_fixes { | 569 | let diagnostic_assists = if include_fixes { |
571 | diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) | 570 | ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) |
572 | .into_iter() | 571 | .into_iter() |
573 | .flat_map(|it| it.fixes.unwrap_or_default()) | 572 | .flat_map(|it| it.fixes.unwrap_or_default()) |
574 | .filter(|it| it.target.intersect(frange.range).is_some()) | 573 | .filter(|it| it.target.intersect(frange.range).is_some()) |
diff --git a/crates/ide_diagnostics/Cargo.toml b/crates/ide_diagnostics/Cargo.toml index 11cd8d570..738fca14e 100644 --- a/crates/ide_diagnostics/Cargo.toml +++ b/crates/ide_diagnostics/Cargo.toml | |||
@@ -10,3 +10,21 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 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 | ide_assists = { path = "../ide_assists", version = "0.0.0" } | ||
26 | |||
27 | [dev-dependencies] | ||
28 | expect-test = "1.1" | ||
29 | |||
30 | test_utils = { path = "../test_utils" } | ||
diff --git a/crates/ide/src/diagnostics/break_outside_of_loop.rs b/crates/ide_diagnostics/src/break_outside_of_loop.rs index 80e68f3cc..79e8cea37 100644 --- a/crates/ide/src/diagnostics/break_outside_of_loop.rs +++ b/crates/ide_diagnostics/src/break_outside_of_loop.rs | |||
@@ -1,4 +1,4 @@ | |||
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 | // |
@@ -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/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/inactive_code.rs b/crates/ide_diagnostics/src/inactive_code.rs index d9d3e88c1..34837cc0d 100644 --- a/crates/ide/src/diagnostics/inactive_code.rs +++ b/crates/ide_diagnostics/src/inactive_code.rs | |||
@@ -1,10 +1,7 @@ | |||
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 | // |
@@ -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/incorrect_case.rs index 832394400..04fc779ce 100644 --- a/crates/ide/src/diagnostics/incorrect_case.rs +++ b/crates/ide_diagnostics/src/incorrect_case.rs | |||
@@ -4,8 +4,10 @@ use ide_db::base_db::FilePosition; | |||
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{ |
7 | diagnostics::{unresolved_fix, Diagnostic, DiagnosticsContext}, | 7 | // references::rename::rename_with_semantics, |
8 | references::rename::rename_with_semantics, | 8 | unresolved_fix, |
9 | Diagnostic, | ||
10 | DiagnosticsContext, | ||
9 | Severity, | 11 | Severity, |
10 | }; | 12 | }; |
11 | 13 | ||
@@ -26,28 +28,34 @@ pub(super) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCas | |||
26 | } | 28 | } |
27 | 29 | ||
28 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> { | 30 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> { |
31 | if true { | ||
32 | return None; | ||
33 | } | ||
34 | |||
29 | let root = ctx.sema.db.parse_or_expand(d.file)?; | 35 | let root = ctx.sema.db.parse_or_expand(d.file)?; |
30 | let name_node = d.ident.to_node(&root); | 36 | let name_node = d.ident.to_node(&root); |
31 | 37 | ||
32 | let name_node = InFile::new(d.file, name_node.syntax()); | 38 | let name_node = InFile::new(d.file, name_node.syntax()); |
33 | let frange = name_node.original_file_range(ctx.sema.db); | 39 | let frange = name_node.original_file_range(ctx.sema.db); |
34 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | 40 | let _file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; |
35 | 41 | ||
36 | let label = format!("Rename to {}", d.suggested_text); | 42 | let label = format!("Rename to {}", d.suggested_text); |
37 | let mut res = unresolved_fix("change_case", &label, frange.range); | 43 | let res = unresolved_fix("change_case", &label, frange.range); |
38 | if ctx.resolve.should_resolve(&res.id) { | 44 | if ctx.resolve.should_resolve(&res.id) { |
39 | let source_change = rename_with_semantics(&ctx.sema, file_position, &d.suggested_text); | 45 | //let source_change = rename_with_semantics(&ctx.sema, file_position, &d.suggested_text); |
40 | res.source_change = Some(source_change.ok().unwrap_or_default()); | 46 | //res.source_change = Some(source_change.ok().unwrap_or_default()); |
47 | todo!() | ||
41 | } | 48 | } |
42 | 49 | ||
43 | Some(vec![res]) | 50 | Some(vec![res]) |
44 | } | 51 | } |
45 | 52 | ||
46 | #[cfg(test)] | 53 | #[cfg(TODO)] |
47 | mod change_case { | 54 | mod change_case { |
48 | use crate::{ | 55 | use crate::{ |
49 | diagnostics::tests::{check_diagnostics, check_fix}, | 56 | fixture, |
50 | fixture, AssistResolveStrategy, DiagnosticsConfig, | 57 | tests::{check_diagnostics, check_fix}, |
58 | AssistResolveStrategy, DiagnosticsConfig, | ||
51 | }; | 59 | }; |
52 | 60 | ||
53 | #[test] | 61 | #[test] |
diff --git a/crates/ide_diagnostics/src/lib.rs b/crates/ide_diagnostics/src/lib.rs index e69de29bb..a104a702d 100644 --- a/crates/ide_diagnostics/src/lib.rs +++ b/crates/ide_diagnostics/src/lib.rs | |||
@@ -0,0 +1,510 @@ | |||
1 | //! Collects diagnostics & fixits for a single file. | ||
2 | //! | ||
3 | //! The tricky bit here is that diagnostics are produced by hir in terms of | ||
4 | //! macro-expanded files, but we need to present them to the users in terms of | ||
5 | //! original files. So we need to map the ranges. | ||
6 | |||
7 | mod break_outside_of_loop; | ||
8 | mod inactive_code; | ||
9 | mod incorrect_case; | ||
10 | mod macro_error; | ||
11 | mod mismatched_arg_count; | ||
12 | mod missing_fields; | ||
13 | mod missing_match_arms; | ||
14 | mod missing_ok_or_some_in_tail_expr; | ||
15 | mod missing_unsafe; | ||
16 | mod no_such_field; | ||
17 | mod remove_this_semicolon; | ||
18 | mod replace_filter_map_next_with_find_map; | ||
19 | mod unimplemented_builtin_macro; | ||
20 | mod unlinked_file; | ||
21 | mod unresolved_extern_crate; | ||
22 | mod unresolved_import; | ||
23 | mod unresolved_macro_call; | ||
24 | mod unresolved_module; | ||
25 | mod unresolved_proc_macro; | ||
26 | |||
27 | mod field_shorthand; | ||
28 | |||
29 | use hir::{diagnostics::AnyDiagnostic, Semantics}; | ||
30 | use ide_assists::AssistResolveStrategy; | ||
31 | use ide_db::{ | ||
32 | base_db::{FileId, SourceDatabase}, | ||
33 | label::Label, | ||
34 | source_change::SourceChange, | ||
35 | RootDatabase, | ||
36 | }; | ||
37 | use itertools::Itertools; | ||
38 | use rustc_hash::FxHashSet; | ||
39 | use syntax::{ | ||
40 | ast::{self, AstNode}, | ||
41 | SyntaxNode, TextRange, | ||
42 | }; | ||
43 | use text_edit::TextEdit; | ||
44 | use unlinked_file::UnlinkedFile; | ||
45 | |||
46 | use ide_assists::{Assist, AssistId, AssistKind}; | ||
47 | |||
48 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
49 | pub struct DiagnosticCode(pub &'static str); | ||
50 | |||
51 | impl DiagnosticCode { | ||
52 | pub fn as_str(&self) -> &str { | ||
53 | self.0 | ||
54 | } | ||
55 | } | ||
56 | |||
57 | #[derive(Debug)] | ||
58 | pub struct Diagnostic { | ||
59 | pub code: DiagnosticCode, | ||
60 | pub message: String, | ||
61 | pub range: TextRange, | ||
62 | pub severity: Severity, | ||
63 | pub unused: bool, | ||
64 | pub experimental: bool, | ||
65 | pub fixes: Option<Vec<Assist>>, | ||
66 | } | ||
67 | |||
68 | impl Diagnostic { | ||
69 | fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic { | ||
70 | let message = message.into(); | ||
71 | Diagnostic { | ||
72 | code: DiagnosticCode(code), | ||
73 | message, | ||
74 | range, | ||
75 | severity: Severity::Error, | ||
76 | unused: false, | ||
77 | experimental: false, | ||
78 | fixes: None, | ||
79 | } | ||
80 | } | ||
81 | |||
82 | fn experimental(mut self) -> Diagnostic { | ||
83 | self.experimental = true; | ||
84 | self | ||
85 | } | ||
86 | |||
87 | fn severity(mut self, severity: Severity) -> Diagnostic { | ||
88 | self.severity = severity; | ||
89 | self | ||
90 | } | ||
91 | |||
92 | fn with_fixes(mut self, fixes: Option<Vec<Assist>>) -> Diagnostic { | ||
93 | self.fixes = fixes; | ||
94 | self | ||
95 | } | ||
96 | |||
97 | fn with_unused(mut self, unused: bool) -> Diagnostic { | ||
98 | self.unused = unused; | ||
99 | self | ||
100 | } | ||
101 | } | ||
102 | |||
103 | #[derive(Debug, Copy, Clone)] | ||
104 | pub enum Severity { | ||
105 | Error, | ||
106 | WeakWarning, | ||
107 | } | ||
108 | |||
109 | #[derive(Default, Debug, Clone)] | ||
110 | pub struct DiagnosticsConfig { | ||
111 | pub disable_experimental: bool, | ||
112 | pub disabled: FxHashSet<String>, | ||
113 | } | ||
114 | |||
115 | struct DiagnosticsContext<'a> { | ||
116 | config: &'a DiagnosticsConfig, | ||
117 | sema: Semantics<'a, RootDatabase>, | ||
118 | resolve: &'a AssistResolveStrategy, | ||
119 | } | ||
120 | |||
121 | pub fn diagnostics( | ||
122 | db: &RootDatabase, | ||
123 | config: &DiagnosticsConfig, | ||
124 | resolve: &AssistResolveStrategy, | ||
125 | file_id: FileId, | ||
126 | ) -> Vec<Diagnostic> { | ||
127 | let _p = profile::span("diagnostics"); | ||
128 | let sema = Semantics::new(db); | ||
129 | let parse = db.parse(file_id); | ||
130 | let mut res = Vec::new(); | ||
131 | |||
132 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. | ||
133 | res.extend( | ||
134 | parse.errors().iter().take(128).map(|err| { | ||
135 | Diagnostic::new("syntax-error", format!("Syntax Error: {}", err), err.range()) | ||
136 | }), | ||
137 | ); | ||
138 | |||
139 | for node in parse.tree().syntax().descendants() { | ||
140 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | ||
141 | field_shorthand::check(&mut res, file_id, &node); | ||
142 | } | ||
143 | |||
144 | let mut diags = Vec::new(); | ||
145 | let module = sema.to_module_def(file_id); | ||
146 | if let Some(m) = module { | ||
147 | m.diagnostics(db, &mut diags) | ||
148 | } | ||
149 | |||
150 | let ctx = DiagnosticsContext { config, sema, resolve }; | ||
151 | if module.is_none() { | ||
152 | let d = UnlinkedFile { file: file_id }; | ||
153 | let d = unlinked_file::unlinked_file(&ctx, &d); | ||
154 | res.push(d) | ||
155 | } | ||
156 | |||
157 | for diag in diags { | ||
158 | #[rustfmt::skip] | ||
159 | let d = match diag { | ||
160 | AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), | ||
161 | AnyDiagnostic::IncorrectCase(d) => incorrect_case::incorrect_case(&ctx, &d), | ||
162 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), | ||
163 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), | ||
164 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), | ||
165 | AnyDiagnostic::MissingMatchArms(d) => missing_match_arms::missing_match_arms(&ctx, &d), | ||
166 | AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), | ||
167 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), | ||
168 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), | ||
169 | AnyDiagnostic::RemoveThisSemicolon(d) => remove_this_semicolon::remove_this_semicolon(&ctx, &d), | ||
170 | AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d), | ||
171 | AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), | ||
172 | AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), | ||
173 | AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), | ||
174 | AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), | ||
175 | AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d), | ||
176 | AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d), | ||
177 | |||
178 | AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) { | ||
179 | Some(it) => it, | ||
180 | None => continue, | ||
181 | } | ||
182 | }; | ||
183 | res.push(d) | ||
184 | } | ||
185 | |||
186 | res.retain(|d| { | ||
187 | !ctx.config.disabled.contains(d.code.as_str()) | ||
188 | && !(ctx.config.disable_experimental && d.experimental) | ||
189 | }); | ||
190 | |||
191 | res | ||
192 | } | ||
193 | |||
194 | fn check_unnecessary_braces_in_use_statement( | ||
195 | acc: &mut Vec<Diagnostic>, | ||
196 | file_id: FileId, | ||
197 | node: &SyntaxNode, | ||
198 | ) -> Option<()> { | ||
199 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | ||
200 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
201 | // If there is a comment inside the bracketed `use`, | ||
202 | // assume it is a commented out module path and don't show diagnostic. | ||
203 | if use_tree_list.has_inner_comment() { | ||
204 | return Some(()); | ||
205 | } | ||
206 | |||
207 | let use_range = use_tree_list.syntax().text_range(); | ||
208 | let edit = | ||
209 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | ||
210 | .unwrap_or_else(|| { | ||
211 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
212 | let mut edit_builder = TextEdit::builder(); | ||
213 | edit_builder.delete(use_range); | ||
214 | edit_builder.insert(use_range.start(), to_replace); | ||
215 | edit_builder.finish() | ||
216 | }); | ||
217 | |||
218 | acc.push( | ||
219 | Diagnostic::new( | ||
220 | "unnecessary-braces", | ||
221 | "Unnecessary braces in use statement".to_string(), | ||
222 | use_range, | ||
223 | ) | ||
224 | .severity(Severity::WeakWarning) | ||
225 | .with_fixes(Some(vec![fix( | ||
226 | "remove_braces", | ||
227 | "Remove unnecessary braces", | ||
228 | SourceChange::from_text_edit(file_id, edit), | ||
229 | use_range, | ||
230 | )])), | ||
231 | ); | ||
232 | } | ||
233 | |||
234 | Some(()) | ||
235 | } | ||
236 | |||
237 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
238 | single_use_tree: &ast::UseTree, | ||
239 | ) -> Option<TextEdit> { | ||
240 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
241 | if single_use_tree.path()?.segment()?.self_token().is_some() { | ||
242 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | ||
243 | let end = use_tree_list_node.text_range().end(); | ||
244 | return Some(TextEdit::delete(TextRange::new(start, end))); | ||
245 | } | ||
246 | None | ||
247 | } | ||
248 | |||
249 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | ||
250 | let mut res = unresolved_fix(id, label, target); | ||
251 | res.source_change = Some(source_change); | ||
252 | res | ||
253 | } | ||
254 | |||
255 | fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | ||
256 | assert!(!id.contains(' ')); | ||
257 | Assist { | ||
258 | id: AssistId(id, AssistKind::QuickFix), | ||
259 | label: Label::new(label), | ||
260 | group: None, | ||
261 | target, | ||
262 | source_change: None, | ||
263 | } | ||
264 | } | ||
265 | |||
266 | #[cfg(test)] | ||
267 | mod tests { | ||
268 | use expect_test::Expect; | ||
269 | use ide_assists::AssistResolveStrategy; | ||
270 | use ide_db::{ | ||
271 | base_db::{fixture::WithFixture, SourceDatabaseExt}, | ||
272 | RootDatabase, | ||
273 | }; | ||
274 | use stdx::trim_indent; | ||
275 | use test_utils::{assert_eq_text, extract_annotations}; | ||
276 | |||
277 | use crate::DiagnosticsConfig; | ||
278 | |||
279 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
280 | /// and checks that: | ||
281 | /// * a diagnostic is produced | ||
282 | /// * the first diagnostic fix trigger range touches the input cursor position | ||
283 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | ||
284 | #[track_caller] | ||
285 | pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
286 | check_nth_fix(0, ra_fixture_before, ra_fixture_after); | ||
287 | } | ||
288 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
289 | /// and checks that: | ||
290 | /// * a diagnostic is produced | ||
291 | /// * every diagnostic fixes trigger range touches the input cursor position | ||
292 | /// * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied | ||
293 | pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) { | ||
294 | for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() { | ||
295 | check_nth_fix(i, ra_fixture_before, ra_fixture_after) | ||
296 | } | ||
297 | } | ||
298 | |||
299 | #[track_caller] | ||
300 | fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { | ||
301 | let after = trim_indent(ra_fixture_after); | ||
302 | |||
303 | let (db, file_position) = RootDatabase::with_position(ra_fixture_before); | ||
304 | let diagnostic = super::diagnostics( | ||
305 | &db, | ||
306 | &DiagnosticsConfig::default(), | ||
307 | &AssistResolveStrategy::All, | ||
308 | file_position.file_id, | ||
309 | ) | ||
310 | .pop() | ||
311 | .expect("no diagnostics"); | ||
312 | let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; | ||
313 | let actual = { | ||
314 | let source_change = fix.source_change.as_ref().unwrap(); | ||
315 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | ||
316 | let mut actual = db.file_text(file_id).to_string(); | ||
317 | |||
318 | for edit in source_change.source_file_edits.values() { | ||
319 | edit.apply(&mut actual); | ||
320 | } | ||
321 | actual | ||
322 | }; | ||
323 | |||
324 | assert_eq_text!(&after, &actual); | ||
325 | assert!( | ||
326 | fix.target.contains_inclusive(file_position.offset), | ||
327 | "diagnostic fix range {:?} does not touch cursor position {:?}", | ||
328 | fix.target, | ||
329 | file_position.offset | ||
330 | ); | ||
331 | } | ||
332 | |||
333 | /// Checks that there's a diagnostic *without* fix at `$0`. | ||
334 | pub(crate) fn check_no_fix(ra_fixture: &str) { | ||
335 | let (db, file_position) = RootDatabase::with_position(ra_fixture); | ||
336 | let diagnostic = super::diagnostics( | ||
337 | &db, | ||
338 | &DiagnosticsConfig::default(), | ||
339 | &AssistResolveStrategy::All, | ||
340 | file_position.file_id, | ||
341 | ) | ||
342 | .pop() | ||
343 | .unwrap(); | ||
344 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); | ||
345 | } | ||
346 | |||
347 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { | ||
348 | let (db, file_id) = RootDatabase::with_single_file(ra_fixture); | ||
349 | let diagnostics = super::diagnostics( | ||
350 | &db, | ||
351 | &DiagnosticsConfig::default(), | ||
352 | &AssistResolveStrategy::All, | ||
353 | file_id, | ||
354 | ); | ||
355 | expect.assert_debug_eq(&diagnostics) | ||
356 | } | ||
357 | |||
358 | #[track_caller] | ||
359 | pub(crate) fn check_diagnostics(ra_fixture: &str) { | ||
360 | let mut config = DiagnosticsConfig::default(); | ||
361 | config.disabled.insert("inactive-code".to_string()); | ||
362 | check_diagnostics_with_config(config, ra_fixture) | ||
363 | } | ||
364 | |||
365 | #[track_caller] | ||
366 | pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) { | ||
367 | let (db, files) = RootDatabase::with_many_files(ra_fixture); | ||
368 | for file_id in files { | ||
369 | let diagnostics = | ||
370 | super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id); | ||
371 | |||
372 | let expected = extract_annotations(&*db.file_text(file_id)); | ||
373 | let mut actual = | ||
374 | diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); | ||
375 | actual.sort_by_key(|(range, _)| range.start()); | ||
376 | assert_eq!(expected, actual); | ||
377 | } | ||
378 | } | ||
379 | |||
380 | #[test] | ||
381 | fn test_check_unnecessary_braces_in_use_statement() { | ||
382 | check_diagnostics( | ||
383 | r#" | ||
384 | use a; | ||
385 | use a::{c, d::e}; | ||
386 | |||
387 | mod a { | ||
388 | mod c {} | ||
389 | mod d { | ||
390 | mod e {} | ||
391 | } | ||
392 | } | ||
393 | "#, | ||
394 | ); | ||
395 | check_diagnostics( | ||
396 | r#" | ||
397 | use a; | ||
398 | use a::{ | ||
399 | c, | ||
400 | // d::e | ||
401 | }; | ||
402 | |||
403 | mod a { | ||
404 | mod c {} | ||
405 | mod d { | ||
406 | mod e {} | ||
407 | } | ||
408 | } | ||
409 | "#, | ||
410 | ); | ||
411 | check_fix( | ||
412 | r" | ||
413 | mod b {} | ||
414 | use {$0b}; | ||
415 | ", | ||
416 | r" | ||
417 | mod b {} | ||
418 | use b; | ||
419 | ", | ||
420 | ); | ||
421 | check_fix( | ||
422 | r" | ||
423 | mod b {} | ||
424 | use {b$0}; | ||
425 | ", | ||
426 | r" | ||
427 | mod b {} | ||
428 | use b; | ||
429 | ", | ||
430 | ); | ||
431 | check_fix( | ||
432 | r" | ||
433 | mod a { mod c {} } | ||
434 | use a::{c$0}; | ||
435 | ", | ||
436 | r" | ||
437 | mod a { mod c {} } | ||
438 | use a::c; | ||
439 | ", | ||
440 | ); | ||
441 | check_fix( | ||
442 | r" | ||
443 | mod a {} | ||
444 | use a::{self$0}; | ||
445 | ", | ||
446 | r" | ||
447 | mod a {} | ||
448 | use a; | ||
449 | ", | ||
450 | ); | ||
451 | check_fix( | ||
452 | r" | ||
453 | mod a { mod c {} mod d { mod e {} } } | ||
454 | use a::{c, d::{e$0}}; | ||
455 | ", | ||
456 | r" | ||
457 | mod a { mod c {} mod d { mod e {} } } | ||
458 | use a::{c, d::e}; | ||
459 | ", | ||
460 | ); | ||
461 | } | ||
462 | |||
463 | #[test] | ||
464 | fn test_disabled_diagnostics() { | ||
465 | let mut config = DiagnosticsConfig::default(); | ||
466 | config.disabled.insert("unresolved-module".into()); | ||
467 | |||
468 | let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#); | ||
469 | |||
470 | let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id); | ||
471 | assert!(diagnostics.is_empty()); | ||
472 | |||
473 | let diagnostics = super::diagnostics( | ||
474 | &db, | ||
475 | &DiagnosticsConfig::default(), | ||
476 | &AssistResolveStrategy::All, | ||
477 | file_id, | ||
478 | ); | ||
479 | assert!(!diagnostics.is_empty()); | ||
480 | } | ||
481 | |||
482 | #[test] | ||
483 | fn import_extern_crate_clash_with_inner_item() { | ||
484 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. | ||
485 | |||
486 | check_diagnostics( | ||
487 | r#" | ||
488 | //- /lib.rs crate:lib deps:jwt | ||
489 | mod permissions; | ||
490 | |||
491 | use permissions::jwt; | ||
492 | |||
493 | fn f() { | ||
494 | fn inner() {} | ||
495 | jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic | ||
496 | } | ||
497 | |||
498 | //- /permissions.rs | ||
499 | pub mod jwt { | ||
500 | pub struct Claims {} | ||
501 | } | ||
502 | |||
503 | //- /jwt/lib.rs crate:jwt | ||
504 | pub struct Claims { | ||
505 | field: u8, | ||
506 | } | ||
507 | "#, | ||
508 | ); | ||
509 | } | ||
510 | } | ||
diff --git a/crates/ide/src/diagnostics/macro_error.rs b/crates/ide_diagnostics/src/macro_error.rs index 5f97f190d..180f297eb 100644 --- a/crates/ide/src/diagnostics/macro_error.rs +++ b/crates/ide_diagnostics/src/macro_error.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: macro-error | 3 | // Diagnostic: macro-error |
4 | // | 4 | // |
@@ -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/mismatched_arg_count.rs index 08e1cfa5f..c5749c8a6 100644 --- a/crates/ide/src/diagnostics/mismatched_arg_count.rs +++ b/crates/ide_diagnostics/src/mismatched_arg_count.rs | |||
@@ -1,4 +1,4 @@ | |||
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 | // |
@@ -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/missing_fields.rs index d01f05041..f242ee481 100644 --- a/crates/ide/src/diagnostics/missing_fields.rs +++ b/crates/ide_diagnostics/src/missing_fields.rs | |||
@@ -6,7 +6,7 @@ use stdx::format_to; | |||
6 | use syntax::{algo, ast::make, AstNode, SyntaxNodePtr}; | 6 | use syntax::{algo, ast::make, AstNode, SyntaxNodePtr}; |
7 | use text_edit::TextEdit; | 7 | use text_edit::TextEdit; |
8 | 8 | ||
9 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | 9 | use crate::{fix, Diagnostic, DiagnosticsContext}; |
10 | 10 | ||
11 | // Diagnostic: missing-fields | 11 | // Diagnostic: missing-fields |
12 | // | 12 | // |
@@ -77,7 +77,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass | |||
77 | 77 | ||
78 | #[cfg(test)] | 78 | #[cfg(test)] |
79 | mod tests { | 79 | mod tests { |
80 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 80 | use crate::tests::{check_diagnostics, check_fix}; |
81 | 81 | ||
82 | #[test] | 82 | #[test] |
83 | fn missing_record_pat_field_diagnostic() { | 83 | fn missing_record_pat_field_diagnostic() { |
diff --git a/crates/ide/src/diagnostics/missing_match_arms.rs b/crates/ide_diagnostics/src/missing_match_arms.rs index b636489b3..c83155d2f 100644 --- a/crates/ide/src/diagnostics/missing_match_arms.rs +++ b/crates/ide_diagnostics/src/missing_match_arms.rs | |||
@@ -1,6 +1,6 @@ | |||
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 | // |
@@ -18,11 +18,11 @@ pub(super) fn missing_match_arms( | |||
18 | 18 | ||
19 | #[cfg(test)] | 19 | #[cfg(test)] |
20 | pub(super) mod tests { | 20 | pub(super) 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/missing_ok_or_some_in_tail_expr.rs index 06005d156..9e36ca296 100644 --- a/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs +++ b/crates/ide_diagnostics/src/missing_ok_or_some_in_tail_expr.rs | |||
@@ -4,7 +4,7 @@ use ide_db::source_change::SourceChange; | |||
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | use text_edit::TextEdit; | 5 | use text_edit::TextEdit; |
6 | 6 | ||
7 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | 7 | use crate::{fix, Diagnostic, DiagnosticsContext}; |
8 | 8 | ||
9 | // Diagnostic: missing-ok-or-some-in-tail-expr | 9 | // Diagnostic: missing-ok-or-some-in-tail-expr |
10 | // | 10 | // |
@@ -44,7 +44,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Op | |||
44 | 44 | ||
45 | #[cfg(test)] | 45 | #[cfg(test)] |
46 | mod tests { | 46 | mod tests { |
47 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | 47 | use crate::tests::{check_diagnostics, check_fix}; |
48 | 48 | ||
49 | #[test] | 49 | #[test] |
50 | fn test_wrap_return_type_option() { | 50 | fn test_wrap_return_type_option() { |
diff --git a/crates/ide/src/diagnostics/missing_unsafe.rs b/crates/ide_diagnostics/src/missing_unsafe.rs index 5c47e8d0a..f5f38a0d3 100644 --- a/crates/ide/src/diagnostics/missing_unsafe.rs +++ b/crates/ide_diagnostics/src/missing_unsafe.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: missing-unsafe | 3 | // Diagnostic: missing-unsafe |
4 | // | 4 | // |
@@ -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/no_such_field.rs index edc63c246..c4fa387ca 100644 --- a/crates/ide/src/diagnostics/no_such_field.rs +++ b/crates/ide_diagnostics/src/no_such_field.rs | |||
@@ -6,10 +6,7 @@ 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 | // |
@@ -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/remove_this_semicolon.rs index 814cb0f8c..dc6c9c083 100644 --- a/crates/ide/src/diagnostics/remove_this_semicolon.rs +++ b/crates/ide_diagnostics/src/remove_this_semicolon.rs | |||
@@ -3,10 +3,7 @@ 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 | // |
@@ -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/replace_filter_map_next_with_find_map.rs index f3b011495..775c350d2 100644 --- a/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs +++ b/crates/ide_diagnostics/src/replace_filter_map_next_with_find_map.rs | |||
@@ -6,10 +6,7 @@ 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 | // |
@@ -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/unimplemented_builtin_macro.rs index 09faa3bbc..a600544f0 100644 --- a/crates/ide/src/diagnostics/unimplemented_builtin_macro.rs +++ b/crates/ide_diagnostics/src/unimplemented_builtin_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: unimplemented-builtin-macro | 3 | // Diagnostic: unimplemented-builtin-macro |
7 | // | 4 | // |
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide_diagnostics/src/unlinked_file.rs index a5b2e3399..424532e3a 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide_diagnostics/src/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 { |
@@ -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/unresolved_extern_crate.rs index 2ea79c2ee..69f07d0b0 100644 --- a/crates/ide/src/diagnostics/unresolved_extern_crate.rs +++ b/crates/ide_diagnostics/src/unresolved_extern_crate.rs | |||
@@ -1,4 +1,4 @@ | |||
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 | // |
@@ -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/unresolved_import.rs index 1cbf96ba1..7779033d4 100644 --- a/crates/ide/src/diagnostics/unresolved_import.rs +++ b/crates/ide_diagnostics/src/unresolved_import.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | 1 | use crate::{Diagnostic, DiagnosticsContext}; |
2 | 2 | ||
3 | // Diagnostic: unresolved-import | 3 | // Diagnostic: unresolved-import |
4 | // | 4 | // |
@@ -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/unresolved_macro_call.rs index 15b6a2730..88133d0f3 100644 --- a/crates/ide/src/diagnostics/unresolved_macro_call.rs +++ b/crates/ide_diagnostics/src/unresolved_macro_call.rs | |||
@@ -1,7 +1,7 @@ | |||
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 | // |
@@ -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/unresolved_module.rs index 977b46414..b11e71b3e 100644 --- a/crates/ide/src/diagnostics/unresolved_module.rs +++ b/crates/ide_diagnostics/src/unresolved_module.rs | |||
@@ -3,7 +3,7 @@ use ide_assists::Assist; | |||
3 | use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit}; | 3 | use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit}; |
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | 5 | ||
6 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | 6 | use crate::{fix, Diagnostic, DiagnosticsContext}; |
7 | 7 | ||
8 | // Diagnostic: unresolved-module | 8 | // Diagnostic: unresolved-module |
9 | // | 9 | // |
@@ -42,7 +42,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Option<Vec< | |||
42 | mod tests { | 42 | mod tests { |
43 | use expect_test::expect; | 43 | use expect_test::expect; |
44 | 44 | ||
45 | use crate::diagnostics::tests::{check_diagnostics, check_expect}; | 45 | use crate::tests::{check_diagnostics, check_expect}; |
46 | 46 | ||
47 | #[test] | 47 | #[test] |
48 | fn unresolved_module() { | 48 | fn unresolved_module() { |
diff --git a/crates/ide/src/diagnostics/unresolved_proc_macro.rs b/crates/ide_diagnostics/src/unresolved_proc_macro.rs index 3dc6ab451..744cce508 100644 --- a/crates/ide/src/diagnostics/unresolved_proc_macro.rs +++ b/crates/ide_diagnostics/src/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 | // |