diff options
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 544 |
1 files changed, 160 insertions, 384 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index d5c954b8b..815a633e5 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -4,69 +4,94 @@ | |||
4 | //! macro-expanded files, but we need to present them to the users 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. | 5 | //! original files. So we need to map the ranges. |
6 | 6 | ||
7 | mod fixes; | 7 | mod break_outside_of_loop; |
8 | mod field_shorthand; | 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; | ||
9 | mod unlinked_file; | 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; | ||
10 | 26 | ||
11 | use std::cell::RefCell; | 27 | mod field_shorthand; |
12 | 28 | ||
13 | use hir::{ | 29 | use hir::{diagnostics::AnyDiagnostic, Semantics}; |
14 | db::AstDatabase, | ||
15 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | ||
16 | InFile, Semantics, | ||
17 | }; | ||
18 | use ide_assists::AssistResolveStrategy; | 30 | use ide_assists::AssistResolveStrategy; |
19 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 31 | use ide_db::{base_db::SourceDatabase, RootDatabase}; |
20 | use itertools::Itertools; | 32 | use itertools::Itertools; |
21 | use rustc_hash::FxHashSet; | 33 | use rustc_hash::FxHashSet; |
22 | use syntax::{ | 34 | use syntax::{ |
23 | ast::{self, AstNode}, | 35 | ast::{self, AstNode}, |
24 | SyntaxNode, SyntaxNodePtr, TextRange, TextSize, | 36 | SyntaxNode, TextRange, |
25 | }; | 37 | }; |
26 | use text_edit::TextEdit; | 38 | use text_edit::TextEdit; |
27 | use unlinked_file::UnlinkedFile; | 39 | use unlinked_file::UnlinkedFile; |
28 | 40 | ||
29 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | 41 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; |
30 | 42 | ||
31 | use self::fixes::DiagnosticWithFixes; | 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 | } | ||
32 | 51 | ||
33 | #[derive(Debug)] | 52 | #[derive(Debug)] |
34 | pub struct Diagnostic { | 53 | pub struct Diagnostic { |
35 | // pub name: Option<String>, | 54 | pub code: DiagnosticCode, |
36 | pub message: String, | 55 | pub message: String, |
37 | pub range: TextRange, | 56 | pub range: TextRange, |
38 | pub severity: Severity, | 57 | pub severity: Severity, |
39 | pub fixes: Option<Vec<Assist>>, | ||
40 | pub unused: bool, | 58 | pub unused: bool, |
41 | pub code: Option<DiagnosticCode>, | 59 | pub experimental: bool, |
60 | pub fixes: Option<Vec<Assist>>, | ||
42 | } | 61 | } |
43 | 62 | ||
44 | impl Diagnostic { | 63 | impl Diagnostic { |
45 | fn error(range: TextRange, message: String) -> Self { | 64 | fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic { |
46 | Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None } | 65 | let message = message.into(); |
47 | } | 66 | Diagnostic { |
48 | 67 | code: DiagnosticCode(code), | |
49 | fn hint(range: TextRange, message: String) -> Self { | ||
50 | Self { | ||
51 | message, | 68 | message, |
52 | range, | 69 | range, |
53 | severity: Severity::WeakWarning, | 70 | severity: Severity::Error, |
54 | fixes: None, | ||
55 | unused: false, | 71 | unused: false, |
56 | code: None, | 72 | experimental: false, |
73 | fixes: None, | ||
57 | } | 74 | } |
58 | } | 75 | } |
59 | 76 | ||
60 | fn with_fixes(self, fixes: Option<Vec<Assist>>) -> Self { | 77 | fn experimental(mut self) -> Diagnostic { |
61 | Self { fixes, ..self } | 78 | self.experimental = true; |
79 | self | ||
80 | } | ||
81 | |||
82 | fn severity(mut self, severity: Severity) -> Diagnostic { | ||
83 | self.severity = severity; | ||
84 | self | ||
62 | } | 85 | } |
63 | 86 | ||
64 | fn with_unused(self, unused: bool) -> Self { | 87 | fn with_fixes(mut self, fixes: Option<Vec<Assist>>) -> Diagnostic { |
65 | Self { unused, ..self } | 88 | self.fixes = fixes; |
89 | self | ||
66 | } | 90 | } |
67 | 91 | ||
68 | fn with_code(self, code: Option<DiagnosticCode>) -> Self { | 92 | fn with_unused(mut self, unused: bool) -> Diagnostic { |
69 | Self { code, ..self } | 93 | self.unused = unused; |
94 | self | ||
70 | } | 95 | } |
71 | } | 96 | } |
72 | 97 | ||
@@ -82,6 +107,12 @@ pub struct DiagnosticsConfig { | |||
82 | pub disabled: FxHashSet<String>, | 107 | pub disabled: FxHashSet<String>, |
83 | } | 108 | } |
84 | 109 | ||
110 | struct DiagnosticsContext<'a> { | ||
111 | config: &'a DiagnosticsConfig, | ||
112 | sema: Semantics<'a, RootDatabase>, | ||
113 | resolve: &'a AssistResolveStrategy, | ||
114 | } | ||
115 | |||
85 | pub(crate) fn diagnostics( | 116 | pub(crate) fn diagnostics( |
86 | db: &RootDatabase, | 117 | db: &RootDatabase, |
87 | config: &DiagnosticsConfig, | 118 | config: &DiagnosticsConfig, |
@@ -95,144 +126,64 @@ pub(crate) fn diagnostics( | |||
95 | 126 | ||
96 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. | 127 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. |
97 | res.extend( | 128 | res.extend( |
98 | parse | 129 | parse.errors().iter().take(128).map(|err| { |
99 | .errors() | 130 | Diagnostic::new("syntax-error", format!("Syntax Error: {}", err), err.range()) |
100 | .iter() | 131 | }), |
101 | .take(128) | ||
102 | .map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))), | ||
103 | ); | 132 | ); |
104 | 133 | ||
105 | for node in parse.tree().syntax().descendants() { | 134 | for node in parse.tree().syntax().descendants() { |
106 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 135 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
107 | field_shorthand::check(&mut res, file_id, &node); | 136 | field_shorthand::check(&mut res, file_id, &node); |
108 | } | 137 | } |
109 | let res = RefCell::new(res); | ||
110 | let sink_builder = DiagnosticSinkBuilder::new() | ||
111 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | ||
112 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
113 | }) | ||
114 | .on::<hir::diagnostics::MissingFields, _>(|d| { | ||
115 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
116 | }) | ||
117 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { | ||
118 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
119 | }) | ||
120 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | ||
121 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
122 | }) | ||
123 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { | ||
124 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
125 | }) | ||
126 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | ||
127 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); | ||
128 | }) | ||
129 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | ||
130 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); | ||
131 | }) | ||
132 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | ||
133 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. | ||
134 | if d.display_source().file_id.expansion_info(db).is_some() { | ||
135 | return; | ||
136 | } | ||
137 | 138 | ||
138 | // Override severity and mark as unused. | 139 | let mut diags = Vec::new(); |
139 | res.borrow_mut().push( | 140 | let module = sema.to_module_def(file_id); |
140 | Diagnostic::hint( | 141 | if let Some(m) = module { |
141 | sema.diagnostics_display_range(d.display_source()).range, | 142 | m.diagnostics(db, &mut diags) |
142 | d.message(), | ||
143 | ) | ||
144 | .with_unused(true) | ||
145 | .with_code(Some(d.code())), | ||
146 | ); | ||
147 | }) | ||
148 | .on::<UnlinkedFile, _>(|d| { | ||
149 | // Limit diagnostic to the first few characters in the file. This matches how VS Code | ||
150 | // renders it with the full span, but on other editors, and is less invasive. | ||
151 | let range = sema.diagnostics_display_range(d.display_source()).range; | ||
152 | let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range); | ||
153 | |||
154 | // Override severity and mark as unused. | ||
155 | res.borrow_mut().push( | ||
156 | Diagnostic::hint(range, d.message()) | ||
157 | .with_fixes(d.fixes(&sema, resolve)) | ||
158 | .with_code(Some(d.code())), | ||
159 | ); | ||
160 | }) | ||
161 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { | ||
162 | // Use more accurate position if available. | ||
163 | let display_range = d | ||
164 | .precise_location | ||
165 | .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range); | ||
166 | |||
167 | // FIXME: it would be nice to tell the user whether proc macros are currently disabled | ||
168 | res.borrow_mut() | ||
169 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); | ||
170 | }) | ||
171 | .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| { | ||
172 | let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| { | ||
173 | d.node | ||
174 | .to_node(&root) | ||
175 | .path() | ||
176 | .and_then(|it| it.segment()) | ||
177 | .and_then(|it| it.name_ref()) | ||
178 | .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax()))) | ||
179 | }); | ||
180 | let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source()); | ||
181 | let display_range = sema.diagnostics_display_range(diagnostics).range; | ||
182 | res.borrow_mut() | ||
183 | .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code()))); | ||
184 | }) | ||
185 | .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| { | ||
186 | let display_range = sema.diagnostics_display_range(d.display_source()).range; | ||
187 | res.borrow_mut() | ||
188 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); | ||
189 | }) | ||
190 | // Only collect experimental diagnostics when they're enabled. | ||
191 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | ||
192 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | ||
193 | |||
194 | // Finalize the `DiagnosticSink` building process. | ||
195 | let mut sink = sink_builder | ||
196 | // Diagnostics not handled above get no fix and default treatment. | ||
197 | .build(|d| { | ||
198 | res.borrow_mut().push( | ||
199 | Diagnostic::error( | ||
200 | sema.diagnostics_display_range(d.display_source()).range, | ||
201 | d.message(), | ||
202 | ) | ||
203 | .with_code(Some(d.code())), | ||
204 | ); | ||
205 | }); | ||
206 | |||
207 | match sema.to_module_def(file_id) { | ||
208 | Some(m) => m.diagnostics(db, &mut sink), | ||
209 | None => { | ||
210 | sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) }); | ||
211 | } | ||
212 | } | 143 | } |
213 | 144 | ||
214 | drop(sink); | 145 | let ctx = DiagnosticsContext { config, sema, resolve }; |
215 | res.into_inner() | 146 | if module.is_none() { |
216 | } | 147 | let d = UnlinkedFile { file: file_id }; |
148 | let d = unlinked_file::unlinked_file(&ctx, &d); | ||
149 | res.push(d) | ||
150 | } | ||
217 | 151 | ||
218 | fn diagnostic_with_fix<D: DiagnosticWithFixes>( | 152 | for diag in diags { |
219 | d: &D, | 153 | #[rustfmt::skip] |
220 | sema: &Semantics<RootDatabase>, | 154 | let d = match diag { |
221 | resolve: &AssistResolveStrategy, | 155 | AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), |
222 | ) -> Diagnostic { | 156 | AnyDiagnostic::IncorrectCase(d) => incorrect_case::incorrect_case(&ctx, &d), |
223 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 157 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), |
224 | .with_fixes(d.fixes(&sema, resolve)) | 158 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), |
225 | .with_code(Some(d.code())) | 159 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), |
226 | } | 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 | } | ||
227 | 180 | ||
228 | fn warning_with_fix<D: DiagnosticWithFixes>( | 181 | res.retain(|d| { |
229 | d: &D, | 182 | !ctx.config.disabled.contains(d.code.as_str()) |
230 | sema: &Semantics<RootDatabase>, | 183 | && !(ctx.config.disable_experimental && d.experimental) |
231 | resolve: &AssistResolveStrategy, | 184 | }); |
232 | ) -> Diagnostic { | 185 | |
233 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 186 | res |
234 | .with_fixes(d.fixes(&sema, resolve)) | ||
235 | .with_code(Some(d.code())) | ||
236 | } | 187 | } |
237 | 188 | ||
238 | fn check_unnecessary_braces_in_use_statement( | 189 | fn check_unnecessary_braces_in_use_statement( |
@@ -260,13 +211,18 @@ fn check_unnecessary_braces_in_use_statement( | |||
260 | }); | 211 | }); |
261 | 212 | ||
262 | acc.push( | 213 | acc.push( |
263 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) | 214 | Diagnostic::new( |
264 | .with_fixes(Some(vec![fix( | 215 | "unnecessary-braces", |
265 | "remove_braces", | 216 | "Unnecessary braces in use statement".to_string(), |
266 | "Remove unnecessary braces", | 217 | use_range, |
267 | SourceChange::from_text_edit(file_id, edit), | 218 | ) |
268 | use_range, | 219 | .severity(Severity::WeakWarning) |
269 | )])), | 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 | )])), | ||
270 | ); | 226 | ); |
271 | } | 227 | } |
272 | 228 | ||
@@ -344,8 +300,8 @@ mod tests { | |||
344 | ) | 300 | ) |
345 | .unwrap() | 301 | .unwrap() |
346 | .pop() | 302 | .pop() |
347 | .unwrap(); | 303 | .expect("no diagnostics"); |
348 | let fix = &diagnostic.fixes.unwrap()[nth]; | 304 | let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; |
349 | let actual = { | 305 | let actual = { |
350 | let source_change = fix.source_change.as_ref().unwrap(); | 306 | let source_change = fix.source_change.as_ref().unwrap(); |
351 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | 307 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); |
@@ -365,8 +321,9 @@ mod tests { | |||
365 | file_position.offset | 321 | file_position.offset |
366 | ); | 322 | ); |
367 | } | 323 | } |
324 | |||
368 | /// Checks that there's a diagnostic *without* fix at `$0`. | 325 | /// Checks that there's a diagnostic *without* fix at `$0`. |
369 | fn check_no_fix(ra_fixture: &str) { | 326 | pub(crate) fn check_no_fix(ra_fixture: &str) { |
370 | let (analysis, file_position) = fixture::position(ra_fixture); | 327 | let (analysis, file_position) = fixture::position(ra_fixture); |
371 | let diagnostic = analysis | 328 | let diagnostic = analysis |
372 | .diagnostics( | 329 | .diagnostics( |
@@ -380,21 +337,6 @@ mod tests { | |||
380 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); | 337 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); |
381 | } | 338 | } |
382 | 339 | ||
383 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | ||
384 | /// apply to the file containing the cursor. | ||
385 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { | ||
386 | let (analysis, files) = fixture::files(ra_fixture); | ||
387 | let diagnostics = files | ||
388 | .into_iter() | ||
389 | .flat_map(|file_id| { | ||
390 | analysis | ||
391 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
392 | .unwrap() | ||
393 | }) | ||
394 | .collect::<Vec<_>>(); | ||
395 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | ||
396 | } | ||
397 | |||
398 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { | 340 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { |
399 | let (analysis, file_id) = fixture::file(ra_fixture); | 341 | let (analysis, file_id) = fixture::file(ra_fixture); |
400 | let diagnostics = analysis | 342 | let diagnostics = analysis |
@@ -403,90 +345,31 @@ mod tests { | |||
403 | expect.assert_debug_eq(&diagnostics) | 345 | expect.assert_debug_eq(&diagnostics) |
404 | } | 346 | } |
405 | 347 | ||
348 | #[track_caller] | ||
406 | pub(crate) fn check_diagnostics(ra_fixture: &str) { | 349 | pub(crate) fn check_diagnostics(ra_fixture: &str) { |
407 | let (analysis, file_id) = fixture::file(ra_fixture); | 350 | let mut config = DiagnosticsConfig::default(); |
408 | let diagnostics = analysis | 351 | config.disabled.insert("inactive-code".to_string()); |
409 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | 352 | check_diagnostics_with_config(config, ra_fixture) |
410 | .unwrap(); | ||
411 | |||
412 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | ||
413 | let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); | ||
414 | assert_eq!(expected, actual); | ||
415 | } | ||
416 | |||
417 | #[test] | ||
418 | fn test_unresolved_macro_range() { | ||
419 | check_diagnostics( | ||
420 | r#" | ||
421 | foo::bar!(92); | ||
422 | //^^^ unresolved macro `foo::bar!` | ||
423 | "#, | ||
424 | ); | ||
425 | } | ||
426 | |||
427 | #[test] | ||
428 | fn unresolved_import_in_use_tree() { | ||
429 | // Only the relevant part of a nested `use` item should be highlighted. | ||
430 | check_diagnostics( | ||
431 | r#" | ||
432 | use does_exist::{Exists, DoesntExist}; | ||
433 | //^^^^^^^^^^^ unresolved import | ||
434 | |||
435 | use {does_not_exist::*, does_exist}; | ||
436 | //^^^^^^^^^^^^^^^^^ unresolved import | ||
437 | |||
438 | use does_not_exist::{ | ||
439 | a, | ||
440 | //^ unresolved import | ||
441 | b, | ||
442 | //^ unresolved import | ||
443 | c, | ||
444 | //^ unresolved import | ||
445 | }; | ||
446 | |||
447 | mod does_exist { | ||
448 | pub struct Exists; | ||
449 | } | ||
450 | "#, | ||
451 | ); | ||
452 | } | 353 | } |
453 | 354 | ||
454 | #[test] | 355 | #[track_caller] |
455 | fn range_mapping_out_of_macros() { | 356 | pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) { |
456 | // FIXME: this is very wrong, but somewhat tricky to fix. | 357 | let (analysis, files) = fixture::files(ra_fixture); |
457 | check_fix( | 358 | for file_id in files { |
458 | r#" | 359 | let diagnostics = |
459 | fn some() {} | 360 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); |
460 | fn items() {} | 361 | |
461 | fn here() {} | 362 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); |
462 | 363 | let mut actual = | |
463 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | 364 | diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); |
464 | 365 | actual.sort_by_key(|(range, _)| range.start()); | |
465 | fn main() { | 366 | assert_eq!(expected, actual); |
466 | let _x = id![Foo { a: $042 }]; | 367 | } |
467 | } | ||
468 | |||
469 | pub struct Foo { pub a: i32, pub b: i32 } | ||
470 | "#, | ||
471 | r#" | ||
472 | fn some(, b: () ) {} | ||
473 | fn items() {} | ||
474 | fn here() {} | ||
475 | |||
476 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
477 | |||
478 | fn main() { | ||
479 | let _x = id![Foo { a: 42 }]; | ||
480 | } | ||
481 | |||
482 | pub struct Foo { pub a: i32, pub b: i32 } | ||
483 | "#, | ||
484 | ); | ||
485 | } | 368 | } |
486 | 369 | ||
487 | #[test] | 370 | #[test] |
488 | fn test_check_unnecessary_braces_in_use_statement() { | 371 | fn test_check_unnecessary_braces_in_use_statement() { |
489 | check_no_diagnostics( | 372 | check_diagnostics( |
490 | r#" | 373 | r#" |
491 | use a; | 374 | use a; |
492 | use a::{c, d::e}; | 375 | use a::{c, d::e}; |
@@ -499,7 +382,7 @@ mod a { | |||
499 | } | 382 | } |
500 | "#, | 383 | "#, |
501 | ); | 384 | ); |
502 | check_no_diagnostics( | 385 | check_diagnostics( |
503 | r#" | 386 | r#" |
504 | use a; | 387 | use a; |
505 | use a::{ | 388 | use a::{ |
@@ -585,138 +468,31 @@ mod a { | |||
585 | } | 468 | } |
586 | 469 | ||
587 | #[test] | 470 | #[test] |
588 | fn unlinked_file_prepend_first_item() { | 471 | fn import_extern_crate_clash_with_inner_item() { |
589 | cov_mark::check!(unlinked_file_prepend_before_first_item); | 472 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. |
590 | // Only tests the first one for `pub mod` since the rest are the same | ||
591 | check_fixes( | ||
592 | r#" | ||
593 | //- /main.rs | ||
594 | fn f() {} | ||
595 | //- /foo.rs | ||
596 | $0 | ||
597 | "#, | ||
598 | vec![ | ||
599 | r#" | ||
600 | mod foo; | ||
601 | |||
602 | fn f() {} | ||
603 | "#, | ||
604 | r#" | ||
605 | pub mod foo; | ||
606 | |||
607 | fn f() {} | ||
608 | "#, | ||
609 | ], | ||
610 | ); | ||
611 | } | ||
612 | |||
613 | #[test] | ||
614 | fn unlinked_file_append_mod() { | ||
615 | cov_mark::check!(unlinked_file_append_to_existing_mods); | ||
616 | check_fix( | ||
617 | r#" | ||
618 | //- /main.rs | ||
619 | //! Comment on top | ||
620 | |||
621 | mod preexisting; | ||
622 | |||
623 | mod preexisting2; | ||
624 | |||
625 | struct S; | ||
626 | |||
627 | mod preexisting_bottom;) | ||
628 | //- /foo.rs | ||
629 | $0 | ||
630 | "#, | ||
631 | r#" | ||
632 | //! Comment on top | ||
633 | 473 | ||
634 | mod preexisting; | 474 | check_diagnostics( |
635 | |||
636 | mod preexisting2; | ||
637 | mod foo; | ||
638 | |||
639 | struct S; | ||
640 | |||
641 | mod preexisting_bottom;) | ||
642 | "#, | ||
643 | ); | ||
644 | } | ||
645 | |||
646 | #[test] | ||
647 | fn unlinked_file_insert_in_empty_file() { | ||
648 | cov_mark::check!(unlinked_file_empty_file); | ||
649 | check_fix( | ||
650 | r#" | ||
651 | //- /main.rs | ||
652 | //- /foo.rs | ||
653 | $0 | ||
654 | "#, | ||
655 | r#" | ||
656 | mod foo; | ||
657 | "#, | ||
658 | ); | ||
659 | } | ||
660 | |||
661 | #[test] | ||
662 | fn unlinked_file_old_style_modrs() { | ||
663 | check_fix( | ||
664 | r#" | ||
665 | //- /main.rs | ||
666 | mod submod; | ||
667 | //- /submod/mod.rs | ||
668 | // in mod.rs | ||
669 | //- /submod/foo.rs | ||
670 | $0 | ||
671 | "#, | ||
672 | r#" | ||
673 | // in mod.rs | ||
674 | mod foo; | ||
675 | "#, | ||
676 | ); | ||
677 | } | ||
678 | |||
679 | #[test] | ||
680 | fn unlinked_file_new_style_mod() { | ||
681 | check_fix( | ||
682 | r#" | ||
683 | //- /main.rs | ||
684 | mod submod; | ||
685 | //- /submod.rs | ||
686 | //- /submod/foo.rs | ||
687 | $0 | ||
688 | "#, | ||
689 | r#" | 475 | r#" |
690 | mod foo; | 476 | //- /lib.rs crate:lib deps:jwt |
691 | "#, | 477 | mod permissions; |
692 | ); | ||
693 | } | ||
694 | 478 | ||
695 | #[test] | 479 | use permissions::jwt; |
696 | fn unlinked_file_with_cfg_off() { | ||
697 | cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists); | ||
698 | check_no_fix( | ||
699 | r#" | ||
700 | //- /main.rs | ||
701 | #[cfg(never)] | ||
702 | mod foo; | ||
703 | 480 | ||
704 | //- /foo.rs | 481 | fn f() { |
705 | $0 | 482 | fn inner() {} |
706 | "#, | 483 | jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic |
707 | ); | 484 | } |
708 | } | ||
709 | 485 | ||
710 | #[test] | 486 | //- /permissions.rs |
711 | fn unlinked_file_with_cfg_on() { | 487 | pub mod jwt { |
712 | check_no_diagnostics( | 488 | pub struct Claims {} |
713 | r#" | 489 | } |
714 | //- /main.rs | ||
715 | #[cfg(not(never))] | ||
716 | mod foo; | ||
717 | 490 | ||
718 | //- /foo.rs | 491 | //- /jwt/lib.rs crate:jwt |
719 | "#, | 492 | pub struct Claims { |
493 | field: u8, | ||
494 | } | ||
495 | "#, | ||
720 | ); | 496 | ); |
721 | } | 497 | } |
722 | } | 498 | } |