diff options
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 751 |
1 files changed, 0 insertions, 751 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs deleted file mode 100644 index 79dbb0865..000000000 --- a/crates/ra_ide/src/diagnostics.rs +++ /dev/null | |||
@@ -1,751 +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 | use std::cell::RefCell; | ||
8 | |||
9 | use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; | ||
10 | use itertools::Itertools; | ||
11 | use ra_db::SourceDatabase; | ||
12 | use ra_ide_db::RootDatabase; | ||
13 | use ra_prof::profile; | ||
14 | use ra_syntax::{ | ||
15 | ast::{self, AstNode}, | ||
16 | SyntaxNode, TextRange, T, | ||
17 | }; | ||
18 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
19 | |||
20 | use crate::{AnalysisConfig, Diagnostic, FileId, Fix, SourceFileEdit}; | ||
21 | |||
22 | mod diagnostics_with_fix; | ||
23 | use diagnostics_with_fix::DiagnosticWithFix; | ||
24 | |||
25 | #[derive(Debug, Copy, Clone)] | ||
26 | pub enum Severity { | ||
27 | Error, | ||
28 | WeakWarning, | ||
29 | } | ||
30 | |||
31 | pub(crate) fn diagnostics( | ||
32 | db: &RootDatabase, | ||
33 | file_id: FileId, | ||
34 | enable_experimental: bool, | ||
35 | analysis_config: &AnalysisConfig, | ||
36 | ) -> Vec<Diagnostic> { | ||
37 | let _p = profile("diagnostics"); | ||
38 | let sema = Semantics::new(db); | ||
39 | let parse = db.parse(file_id); | ||
40 | let mut res = Vec::new(); | ||
41 | |||
42 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. | ||
43 | res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { | ||
44 | name: None, | ||
45 | range: err.range(), | ||
46 | message: format!("Syntax Error: {}", err), | ||
47 | severity: Severity::Error, | ||
48 | fix: None, | ||
49 | })); | ||
50 | |||
51 | for node in parse.tree().syntax().descendants() { | ||
52 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | ||
53 | check_struct_shorthand_initialization(&mut res, file_id, &node); | ||
54 | } | ||
55 | let res = RefCell::new(res); | ||
56 | let mut sink_builder = DiagnosticSinkBuilder::new() | ||
57 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | ||
58 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | ||
59 | }) | ||
60 | .on::<hir::diagnostics::MissingFields, _>(|d| { | ||
61 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | ||
62 | }) | ||
63 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | ||
64 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | ||
65 | }) | ||
66 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | ||
67 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | ||
68 | }) | ||
69 | // Only collect experimental diagnostics when they're enabled. | ||
70 | .filter(|diag| !diag.is_experimental() || enable_experimental); | ||
71 | |||
72 | if !analysis_config.disabled_diagnostics.is_empty() { | ||
73 | // Do not collect disabled diagnostics. | ||
74 | sink_builder = | ||
75 | sink_builder.filter(|diag| !analysis_config.disabled_diagnostics.contains(diag.name())); | ||
76 | } | ||
77 | |||
78 | // Finalize the `DiagnosticSink` building process. | ||
79 | let mut sink = sink_builder | ||
80 | // Diagnostics not handled above get no fix and default treatment. | ||
81 | .build(|d| { | ||
82 | res.borrow_mut().push(Diagnostic { | ||
83 | name: Some(d.name().into()), | ||
84 | message: d.message(), | ||
85 | range: sema.diagnostics_display_range(d).range, | ||
86 | severity: Severity::Error, | ||
87 | fix: None, | ||
88 | }) | ||
89 | }); | ||
90 | |||
91 | if let Some(m) = sema.to_module_def(file_id) { | ||
92 | m.diagnostics(db, &mut sink); | ||
93 | }; | ||
94 | drop(sink); | ||
95 | res.into_inner() | ||
96 | } | ||
97 | |||
98 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | ||
99 | Diagnostic { | ||
100 | name: Some(d.name().into()), | ||
101 | range: sema.diagnostics_display_range(d).range, | ||
102 | message: d.message(), | ||
103 | severity: Severity::Error, | ||
104 | fix: d.fix(&sema), | ||
105 | } | ||
106 | } | ||
107 | |||
108 | fn check_unnecessary_braces_in_use_statement( | ||
109 | acc: &mut Vec<Diagnostic>, | ||
110 | file_id: FileId, | ||
111 | node: &SyntaxNode, | ||
112 | ) -> Option<()> { | ||
113 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | ||
114 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
115 | let use_range = use_tree_list.syntax().text_range(); | ||
116 | let edit = | ||
117 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | ||
118 | .unwrap_or_else(|| { | ||
119 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
120 | let mut edit_builder = TextEditBuilder::default(); | ||
121 | edit_builder.delete(use_range); | ||
122 | edit_builder.insert(use_range.start(), to_replace); | ||
123 | edit_builder.finish() | ||
124 | }); | ||
125 | |||
126 | acc.push(Diagnostic { | ||
127 | name: None, | ||
128 | range: use_range, | ||
129 | message: "Unnecessary braces in use statement".to_string(), | ||
130 | severity: Severity::WeakWarning, | ||
131 | fix: Some(Fix::new( | ||
132 | "Remove unnecessary braces", | ||
133 | SourceFileEdit { file_id, edit }.into(), | ||
134 | use_range, | ||
135 | )), | ||
136 | }); | ||
137 | } | ||
138 | |||
139 | Some(()) | ||
140 | } | ||
141 | |||
142 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
143 | single_use_tree: &ast::UseTree, | ||
144 | ) -> Option<TextEdit> { | ||
145 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
146 | if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { | ||
147 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | ||
148 | let end = use_tree_list_node.text_range().end(); | ||
149 | return Some(TextEdit::delete(TextRange::new(start, end))); | ||
150 | } | ||
151 | None | ||
152 | } | ||
153 | |||
154 | fn check_struct_shorthand_initialization( | ||
155 | acc: &mut Vec<Diagnostic>, | ||
156 | file_id: FileId, | ||
157 | node: &SyntaxNode, | ||
158 | ) -> Option<()> { | ||
159 | let record_lit = ast::RecordExpr::cast(node.clone())?; | ||
160 | let record_field_list = record_lit.record_expr_field_list()?; | ||
161 | for record_field in record_field_list.fields() { | ||
162 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
163 | let field_name = name_ref.syntax().text().to_string(); | ||
164 | let field_expr = expr.syntax().text().to_string(); | ||
165 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
166 | if field_name == field_expr && !field_name_is_tup_index { | ||
167 | let mut edit_builder = TextEditBuilder::default(); | ||
168 | edit_builder.delete(record_field.syntax().text_range()); | ||
169 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
170 | let edit = edit_builder.finish(); | ||
171 | |||
172 | let field_range = record_field.syntax().text_range(); | ||
173 | acc.push(Diagnostic { | ||
174 | name: None, | ||
175 | range: field_range, | ||
176 | message: "Shorthand struct initialization".to_string(), | ||
177 | severity: Severity::WeakWarning, | ||
178 | fix: Some(Fix::new( | ||
179 | "Use struct shorthand initialization", | ||
180 | SourceFileEdit { file_id, edit }.into(), | ||
181 | field_range, | ||
182 | )), | ||
183 | }); | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | Some(()) | ||
188 | } | ||
189 | |||
190 | #[cfg(test)] | ||
191 | mod tests { | ||
192 | use std::collections::HashSet; | ||
193 | use stdx::trim_indent; | ||
194 | use test_utils::assert_eq_text; | ||
195 | |||
196 | use crate::{ | ||
197 | mock_analysis::{analysis_and_position, single_file, MockAnalysis}, | ||
198 | AnalysisConfig, | ||
199 | }; | ||
200 | use expect::{expect, Expect}; | ||
201 | |||
202 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
203 | /// and checks that: | ||
204 | /// * a diagnostic is produced | ||
205 | /// * this diagnostic fix trigger range touches the input cursor position | ||
206 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | ||
207 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
208 | let after = trim_indent(ra_fixture_after); | ||
209 | |||
210 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); | ||
211 | let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap(); | ||
212 | let mut fix = diagnostic.fix.unwrap(); | ||
213 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | ||
214 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | ||
215 | let actual = { | ||
216 | let mut actual = target_file_contents.to_string(); | ||
217 | edit.apply(&mut actual); | ||
218 | actual | ||
219 | }; | ||
220 | |||
221 | assert_eq_text!(&after, &actual); | ||
222 | assert!( | ||
223 | fix.fix_trigger_range.start() <= file_position.offset | ||
224 | && fix.fix_trigger_range.end() >= file_position.offset, | ||
225 | "diagnostic fix range {:?} does not touch cursor position {:?}", | ||
226 | fix.fix_trigger_range, | ||
227 | file_position.offset | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | /// Checks that a diagnostic applies to the file containing the `<|>` cursor marker | ||
232 | /// which has a fix that can apply to other files. | ||
233 | fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
234 | let ra_fixture_after = &trim_indent(ra_fixture_after); | ||
235 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); | ||
236 | let current_file_id = file_pos.file_id; | ||
237 | let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap(); | ||
238 | let mut fix = diagnostic.fix.unwrap(); | ||
239 | let edit = fix.source_change.source_file_edits.pop().unwrap(); | ||
240 | let changed_file_id = edit.file_id; | ||
241 | let before = analysis.file_text(changed_file_id).unwrap(); | ||
242 | let actual = { | ||
243 | let mut actual = before.to_string(); | ||
244 | edit.edit.apply(&mut actual); | ||
245 | actual | ||
246 | }; | ||
247 | assert_eq_text!(ra_fixture_after, &actual); | ||
248 | } | ||
249 | |||
250 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | ||
251 | /// apply to the file containing the cursor. | ||
252 | fn check_no_diagnostics(ra_fixture: &str) { | ||
253 | let mock = MockAnalysis::with_files(ra_fixture); | ||
254 | let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>(); | ||
255 | let analysis = mock.analysis(); | ||
256 | let diagnostics = files | ||
257 | .into_iter() | ||
258 | .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap()) | ||
259 | .collect::<Vec<_>>(); | ||
260 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | ||
261 | } | ||
262 | |||
263 | /// Takes a multi-file input fixture with annotated cursor position and the list of disabled diagnostics, | ||
264 | /// and checks that provided diagnostics aren't spawned during analysis. | ||
265 | fn check_disabled_diagnostics(ra_fixture: &str, disabled_diagnostics: &[&'static str]) { | ||
266 | let disabled_diagnostics: HashSet<_> = | ||
267 | disabled_diagnostics.into_iter().map(|diag| diag.to_string()).collect(); | ||
268 | |||
269 | let mock = MockAnalysis::with_files(ra_fixture); | ||
270 | let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>(); | ||
271 | let mut analysis = mock.analysis(); | ||
272 | analysis.set_config(AnalysisConfig { disabled_diagnostics: disabled_diagnostics.clone() }); | ||
273 | |||
274 | let diagnostics = files | ||
275 | .clone() | ||
276 | .into_iter() | ||
277 | .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap()) | ||
278 | .collect::<Vec<_>>(); | ||
279 | |||
280 | // First, we have to check that diagnostic is not emitted when it's added to the disabled diagnostics list. | ||
281 | for diagnostic in diagnostics { | ||
282 | if let Some(name) = diagnostic.name { | ||
283 | assert!(!disabled_diagnostics.contains(&name), "Diagnostic {} is disabled", name); | ||
284 | } | ||
285 | } | ||
286 | |||
287 | // Then, we must reset the config and repeat the check, so that we'll be sure that without | ||
288 | // config these diagnostics are emitted. | ||
289 | // This is required for tests to not become outdated if e.g. diagnostics name changes: | ||
290 | // without this additional run the test will pass simply because a diagnostic with an old name | ||
291 | // will no longer exist. | ||
292 | analysis.set_config(AnalysisConfig { disabled_diagnostics: Default::default() }); | ||
293 | |||
294 | let diagnostics = files | ||
295 | .into_iter() | ||
296 | .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap()) | ||
297 | .collect::<Vec<_>>(); | ||
298 | |||
299 | assert!( | ||
300 | diagnostics | ||
301 | .into_iter() | ||
302 | .filter_map(|diag| diag.name) | ||
303 | .any(|name| disabled_diagnostics.contains(&name)), | ||
304 | "At least one of the diagnostics was not emitted even without config; are the diagnostics names correct?" | ||
305 | ); | ||
306 | } | ||
307 | |||
308 | fn check_expect(ra_fixture: &str, expect: Expect) { | ||
309 | let (analysis, file_id) = single_file(ra_fixture); | ||
310 | let diagnostics = analysis.diagnostics(file_id, true).unwrap(); | ||
311 | expect.assert_debug_eq(&diagnostics) | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn test_wrap_return_type() { | ||
316 | check_fix( | ||
317 | r#" | ||
318 | //- /main.rs | ||
319 | use core::result::Result::{self, Ok, Err}; | ||
320 | |||
321 | fn div(x: i32, y: i32) -> Result<i32, ()> { | ||
322 | if y == 0 { | ||
323 | return Err(()); | ||
324 | } | ||
325 | x / y<|> | ||
326 | } | ||
327 | //- /core/lib.rs | ||
328 | pub mod result { | ||
329 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
330 | } | ||
331 | "#, | ||
332 | r#" | ||
333 | use core::result::Result::{self, Ok, Err}; | ||
334 | |||
335 | fn div(x: i32, y: i32) -> Result<i32, ()> { | ||
336 | if y == 0 { | ||
337 | return Err(()); | ||
338 | } | ||
339 | Ok(x / y) | ||
340 | } | ||
341 | "#, | ||
342 | ); | ||
343 | } | ||
344 | |||
345 | #[test] | ||
346 | fn test_wrap_return_type_handles_generic_functions() { | ||
347 | check_fix( | ||
348 | r#" | ||
349 | //- /main.rs | ||
350 | use core::result::Result::{self, Ok, Err}; | ||
351 | |||
352 | fn div<T>(x: T) -> Result<T, i32> { | ||
353 | if x == 0 { | ||
354 | return Err(7); | ||
355 | } | ||
356 | <|>x | ||
357 | } | ||
358 | //- /core/lib.rs | ||
359 | pub mod result { | ||
360 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
361 | } | ||
362 | "#, | ||
363 | r#" | ||
364 | use core::result::Result::{self, Ok, Err}; | ||
365 | |||
366 | fn div<T>(x: T) -> Result<T, i32> { | ||
367 | if x == 0 { | ||
368 | return Err(7); | ||
369 | } | ||
370 | Ok(x) | ||
371 | } | ||
372 | "#, | ||
373 | ); | ||
374 | } | ||
375 | |||
376 | #[test] | ||
377 | fn test_wrap_return_type_handles_type_aliases() { | ||
378 | check_fix( | ||
379 | r#" | ||
380 | //- /main.rs | ||
381 | use core::result::Result::{self, Ok, Err}; | ||
382 | |||
383 | type MyResult<T> = Result<T, ()>; | ||
384 | |||
385 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
386 | if y == 0 { | ||
387 | return Err(()); | ||
388 | } | ||
389 | x <|>/ y | ||
390 | } | ||
391 | //- /core/lib.rs | ||
392 | pub mod result { | ||
393 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
394 | } | ||
395 | "#, | ||
396 | r#" | ||
397 | use core::result::Result::{self, Ok, Err}; | ||
398 | |||
399 | type MyResult<T> = Result<T, ()>; | ||
400 | |||
401 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
402 | if y == 0 { | ||
403 | return Err(()); | ||
404 | } | ||
405 | Ok(x / y) | ||
406 | } | ||
407 | "#, | ||
408 | ); | ||
409 | } | ||
410 | |||
411 | #[test] | ||
412 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { | ||
413 | check_no_diagnostics( | ||
414 | r#" | ||
415 | //- /main.rs | ||
416 | use core::result::Result::{self, Ok, Err}; | ||
417 | |||
418 | fn foo() -> Result<(), i32> { 0 } | ||
419 | |||
420 | //- /core/lib.rs | ||
421 | pub mod result { | ||
422 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
423 | } | ||
424 | "#, | ||
425 | ); | ||
426 | } | ||
427 | |||
428 | #[test] | ||
429 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { | ||
430 | check_no_diagnostics( | ||
431 | r#" | ||
432 | //- /main.rs | ||
433 | use core::result::Result::{self, Ok, Err}; | ||
434 | |||
435 | enum SomeOtherEnum { Ok(i32), Err(String) } | ||
436 | |||
437 | fn foo() -> SomeOtherEnum { 0 } | ||
438 | |||
439 | //- /core/lib.rs | ||
440 | pub mod result { | ||
441 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
442 | } | ||
443 | "#, | ||
444 | ); | ||
445 | } | ||
446 | |||
447 | #[test] | ||
448 | fn test_fill_struct_fields_empty() { | ||
449 | check_fix( | ||
450 | r#" | ||
451 | struct TestStruct { one: i32, two: i64 } | ||
452 | |||
453 | fn test_fn() { | ||
454 | let s = TestStruct {<|>}; | ||
455 | } | ||
456 | "#, | ||
457 | r#" | ||
458 | struct TestStruct { one: i32, two: i64 } | ||
459 | |||
460 | fn test_fn() { | ||
461 | let s = TestStruct { one: (), two: ()}; | ||
462 | } | ||
463 | "#, | ||
464 | ); | ||
465 | } | ||
466 | |||
467 | #[test] | ||
468 | fn test_fill_struct_fields_self() { | ||
469 | check_fix( | ||
470 | r#" | ||
471 | struct TestStruct { one: i32 } | ||
472 | |||
473 | impl TestStruct { | ||
474 | fn test_fn() { let s = Self {<|>}; } | ||
475 | } | ||
476 | "#, | ||
477 | r#" | ||
478 | struct TestStruct { one: i32 } | ||
479 | |||
480 | impl TestStruct { | ||
481 | fn test_fn() { let s = Self { one: ()}; } | ||
482 | } | ||
483 | "#, | ||
484 | ); | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn test_fill_struct_fields_enum() { | ||
489 | check_fix( | ||
490 | r#" | ||
491 | enum Expr { | ||
492 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
493 | } | ||
494 | |||
495 | impl Expr { | ||
496 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
497 | Expr::Bin {<|> } | ||
498 | } | ||
499 | } | ||
500 | "#, | ||
501 | r#" | ||
502 | enum Expr { | ||
503 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
504 | } | ||
505 | |||
506 | impl Expr { | ||
507 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
508 | Expr::Bin { lhs: (), rhs: () } | ||
509 | } | ||
510 | } | ||
511 | "#, | ||
512 | ); | ||
513 | } | ||
514 | |||
515 | #[test] | ||
516 | fn test_fill_struct_fields_partial() { | ||
517 | check_fix( | ||
518 | r#" | ||
519 | struct TestStruct { one: i32, two: i64 } | ||
520 | |||
521 | fn test_fn() { | ||
522 | let s = TestStruct{ two: 2<|> }; | ||
523 | } | ||
524 | "#, | ||
525 | r" | ||
526 | struct TestStruct { one: i32, two: i64 } | ||
527 | |||
528 | fn test_fn() { | ||
529 | let s = TestStruct{ two: 2, one: () }; | ||
530 | } | ||
531 | ", | ||
532 | ); | ||
533 | } | ||
534 | |||
535 | #[test] | ||
536 | fn test_fill_struct_fields_no_diagnostic() { | ||
537 | check_no_diagnostics( | ||
538 | r" | ||
539 | struct TestStruct { one: i32, two: i64 } | ||
540 | |||
541 | fn test_fn() { | ||
542 | let one = 1; | ||
543 | let s = TestStruct{ one, two: 2 }; | ||
544 | } | ||
545 | ", | ||
546 | ); | ||
547 | } | ||
548 | |||
549 | #[test] | ||
550 | fn test_fill_struct_fields_no_diagnostic_on_spread() { | ||
551 | check_no_diagnostics( | ||
552 | r" | ||
553 | struct TestStruct { one: i32, two: i64 } | ||
554 | |||
555 | fn test_fn() { | ||
556 | let one = 1; | ||
557 | let s = TestStruct{ ..a }; | ||
558 | } | ||
559 | ", | ||
560 | ); | ||
561 | } | ||
562 | |||
563 | #[test] | ||
564 | fn test_unresolved_module_diagnostic() { | ||
565 | check_expect( | ||
566 | r#"mod foo;"#, | ||
567 | expect![[r#" | ||
568 | [ | ||
569 | Diagnostic { | ||
570 | name: Some( | ||
571 | "unresolved-module", | ||
572 | ), | ||
573 | message: "unresolved module", | ||
574 | range: 0..8, | ||
575 | severity: Error, | ||
576 | fix: Some( | ||
577 | Fix { | ||
578 | label: "Create module", | ||
579 | source_change: SourceChange { | ||
580 | source_file_edits: [], | ||
581 | file_system_edits: [ | ||
582 | CreateFile { | ||
583 | anchor: FileId( | ||
584 | 1, | ||
585 | ), | ||
586 | dst: "foo.rs", | ||
587 | }, | ||
588 | ], | ||
589 | is_snippet: false, | ||
590 | }, | ||
591 | fix_trigger_range: 0..8, | ||
592 | }, | ||
593 | ), | ||
594 | }, | ||
595 | ] | ||
596 | "#]], | ||
597 | ); | ||
598 | } | ||
599 | |||
600 | #[test] | ||
601 | fn range_mapping_out_of_macros() { | ||
602 | // FIXME: this is very wrong, but somewhat tricky to fix. | ||
603 | check_fix( | ||
604 | r#" | ||
605 | fn some() {} | ||
606 | fn items() {} | ||
607 | fn here() {} | ||
608 | |||
609 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
610 | |||
611 | fn main() { | ||
612 | let _x = id![Foo { a: <|>42 }]; | ||
613 | } | ||
614 | |||
615 | pub struct Foo { pub a: i32, pub b: i32 } | ||
616 | "#, | ||
617 | r#" | ||
618 | fn {a:42, b: ()} {} | ||
619 | fn items() {} | ||
620 | fn here() {} | ||
621 | |||
622 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
623 | |||
624 | fn main() { | ||
625 | let _x = id![Foo { a: 42 }]; | ||
626 | } | ||
627 | |||
628 | pub struct Foo { pub a: i32, pub b: i32 } | ||
629 | "#, | ||
630 | ); | ||
631 | } | ||
632 | |||
633 | #[test] | ||
634 | fn test_check_unnecessary_braces_in_use_statement() { | ||
635 | check_no_diagnostics( | ||
636 | r#" | ||
637 | use a; | ||
638 | use a::{c, d::e}; | ||
639 | "#, | ||
640 | ); | ||
641 | check_fix(r#"use {<|>b};"#, r#"use b;"#); | ||
642 | check_fix(r#"use {b<|>};"#, r#"use b;"#); | ||
643 | check_fix(r#"use a::{c<|>};"#, r#"use a::c;"#); | ||
644 | check_fix(r#"use a::{self<|>};"#, r#"use a;"#); | ||
645 | check_fix(r#"use a::{c, d::{e<|>}};"#, r#"use a::{c, d::e};"#); | ||
646 | } | ||
647 | |||
648 | #[test] | ||
649 | fn test_check_struct_shorthand_initialization() { | ||
650 | check_no_diagnostics( | ||
651 | r#" | ||
652 | struct A { a: &'static str } | ||
653 | fn main() { A { a: "hello" } } | ||
654 | "#, | ||
655 | ); | ||
656 | check_no_diagnostics( | ||
657 | r#" | ||
658 | struct A(usize); | ||
659 | fn main() { A { 0: 0 } } | ||
660 | "#, | ||
661 | ); | ||
662 | |||
663 | check_fix( | ||
664 | r#" | ||
665 | struct A { a: &'static str } | ||
666 | fn main() { | ||
667 | let a = "haha"; | ||
668 | A { a<|>: a } | ||
669 | } | ||
670 | "#, | ||
671 | r#" | ||
672 | struct A { a: &'static str } | ||
673 | fn main() { | ||
674 | let a = "haha"; | ||
675 | A { a } | ||
676 | } | ||
677 | "#, | ||
678 | ); | ||
679 | |||
680 | check_fix( | ||
681 | r#" | ||
682 | struct A { a: &'static str, b: &'static str } | ||
683 | fn main() { | ||
684 | let a = "haha"; | ||
685 | let b = "bb"; | ||
686 | A { a<|>: a, b } | ||
687 | } | ||
688 | "#, | ||
689 | r#" | ||
690 | struct A { a: &'static str, b: &'static str } | ||
691 | fn main() { | ||
692 | let a = "haha"; | ||
693 | let b = "bb"; | ||
694 | A { a, b } | ||
695 | } | ||
696 | "#, | ||
697 | ); | ||
698 | } | ||
699 | |||
700 | #[test] | ||
701 | fn test_add_field_from_usage() { | ||
702 | check_fix( | ||
703 | r" | ||
704 | fn main() { | ||
705 | Foo { bar: 3, baz<|>: false}; | ||
706 | } | ||
707 | struct Foo { | ||
708 | bar: i32 | ||
709 | } | ||
710 | ", | ||
711 | r" | ||
712 | fn main() { | ||
713 | Foo { bar: 3, baz: false}; | ||
714 | } | ||
715 | struct Foo { | ||
716 | bar: i32, | ||
717 | baz: bool | ||
718 | } | ||
719 | ", | ||
720 | ) | ||
721 | } | ||
722 | |||
723 | #[test] | ||
724 | fn test_add_field_in_other_file_from_usage() { | ||
725 | check_apply_diagnostic_fix_in_other_file( | ||
726 | r" | ||
727 | //- /main.rs | ||
728 | mod foo; | ||
729 | |||
730 | fn main() { | ||
731 | <|>foo::Foo { bar: 3, baz: false}; | ||
732 | } | ||
733 | //- /foo.rs | ||
734 | struct Foo { | ||
735 | bar: i32 | ||
736 | } | ||
737 | ", | ||
738 | r" | ||
739 | struct Foo { | ||
740 | bar: i32, | ||
741 | pub(crate) baz: bool | ||
742 | } | ||
743 | ", | ||
744 | ) | ||
745 | } | ||
746 | |||
747 | #[test] | ||
748 | fn test_disabled_diagnostics() { | ||
749 | check_disabled_diagnostics(r#"mod foo;"#, &["unresolved-module"]); | ||
750 | } | ||
751 | } | ||