aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r--crates/ide/src/diagnostics.rs544
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
7mod fixes; 7mod break_outside_of_loop;
8mod field_shorthand; 8mod inactive_code;
9mod incorrect_case;
10mod macro_error;
11mod mismatched_arg_count;
12mod missing_fields;
13mod missing_match_arms;
14mod missing_ok_or_some_in_tail_expr;
15mod missing_unsafe;
16mod no_such_field;
17mod remove_this_semicolon;
18mod replace_filter_map_next_with_find_map;
19mod unimplemented_builtin_macro;
9mod unlinked_file; 20mod unlinked_file;
21mod unresolved_extern_crate;
22mod unresolved_import;
23mod unresolved_macro_call;
24mod unresolved_module;
25mod unresolved_proc_macro;
10 26
11use std::cell::RefCell; 27mod field_shorthand;
12 28
13use hir::{ 29use hir::{diagnostics::AnyDiagnostic, Semantics};
14 db::AstDatabase,
15 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
16 InFile, Semantics,
17};
18use ide_assists::AssistResolveStrategy; 30use ide_assists::AssistResolveStrategy;
19use ide_db::{base_db::SourceDatabase, RootDatabase}; 31use ide_db::{base_db::SourceDatabase, RootDatabase};
20use itertools::Itertools; 32use itertools::Itertools;
21use rustc_hash::FxHashSet; 33use rustc_hash::FxHashSet;
22use syntax::{ 34use syntax::{
23 ast::{self, AstNode}, 35 ast::{self, AstNode},
24 SyntaxNode, SyntaxNodePtr, TextRange, TextSize, 36 SyntaxNode, TextRange,
25}; 37};
26use text_edit::TextEdit; 38use text_edit::TextEdit;
27use unlinked_file::UnlinkedFile; 39use unlinked_file::UnlinkedFile;
28 40
29use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; 41use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
30 42
31use self::fixes::DiagnosticWithFixes; 43#[derive(Copy, Clone, Debug, PartialEq)]
44pub struct DiagnosticCode(pub &'static str);
45
46impl DiagnosticCode {
47 pub fn as_str(&self) -> &str {
48 self.0
49 }
50}
32 51
33#[derive(Debug)] 52#[derive(Debug)]
34pub struct Diagnostic { 53pub 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
44impl Diagnostic { 63impl 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
110struct DiagnosticsContext<'a> {
111 config: &'a DiagnosticsConfig,
112 sema: Semantics<'a, RootDatabase>,
113 resolve: &'a AssistResolveStrategy,
114}
115
85pub(crate) fn diagnostics( 116pub(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
218fn 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
228fn 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
238fn check_unnecessary_braces_in_use_statement( 189fn 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#"
421foo::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#"
432use does_exist::{Exists, DoesntExist};
433 //^^^^^^^^^^^ unresolved import
434
435use {does_not_exist::*, does_exist};
436 //^^^^^^^^^^^^^^^^^ unresolved import
437
438use does_not_exist::{
439 a,
440 //^ unresolved import
441 b,
442 //^ unresolved import
443 c,
444 //^ unresolved import
445};
446
447mod 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 =
459fn some() {} 360 analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap();
460fn items() {} 361
461fn here() {} 362 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
462 363 let mut actual =
463macro_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());
465fn main() { 366 assert_eq!(expected, actual);
466 let _x = id![Foo { a: $042 }]; 367 }
467}
468
469pub struct Foo { pub a: i32, pub b: i32 }
470"#,
471 r#"
472fn some(, b: () ) {}
473fn items() {}
474fn here() {}
475
476macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
477
478fn main() {
479 let _x = id![Foo { a: 42 }];
480}
481
482pub 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#"
491use a; 374use a;
492use a::{c, d::e}; 375use 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#"
504use a; 387use a;
505use a::{ 388use 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
594fn f() {}
595//- /foo.rs
596$0
597"#,
598 vec![
599 r#"
600mod foo;
601
602fn f() {}
603"#,
604 r#"
605pub mod foo;
606
607fn 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
621mod preexisting;
622
623mod preexisting2;
624
625struct S;
626
627mod preexisting_bottom;)
628//- /foo.rs
629$0
630"#,
631 r#"
632//! Comment on top
633 473
634mod preexisting; 474 check_diagnostics(
635
636mod preexisting2;
637mod foo;
638
639struct S;
640
641mod 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#"
656mod foo;
657"#,
658 );
659 }
660
661 #[test]
662 fn unlinked_file_old_style_modrs() {
663 check_fix(
664 r#"
665//- /main.rs
666mod submod;
667//- /submod/mod.rs
668// in mod.rs
669//- /submod/foo.rs
670$0
671"#,
672 r#"
673// in mod.rs
674mod foo;
675"#,
676 );
677 }
678
679 #[test]
680 fn unlinked_file_new_style_mod() {
681 check_fix(
682 r#"
683//- /main.rs
684mod submod;
685//- /submod.rs
686//- /submod/foo.rs
687$0
688"#,
689 r#" 475 r#"
690mod foo; 476//- /lib.rs crate:lib deps:jwt
691"#, 477mod permissions;
692 );
693 }
694 478
695 #[test] 479use 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)]
702mod foo;
703 480
704//- /foo.rs 481fn 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() { 487pub mod jwt {
712 check_no_diagnostics( 488 pub struct Claims {}
713 r#" 489}
714//- /main.rs
715#[cfg(not(never))]
716mod foo;
717 490
718//- /foo.rs 491//- /jwt/lib.rs crate:jwt
719"#, 492pub struct Claims {
493 field: u8,
494}
495 "#,
720 ); 496 );
721 } 497 }
722} 498}