1 use codespan_reporting::diagnostic::{Diagnostic, Label};
2 use codespan_reporting::files::{SimpleFile, SimpleFiles};
3 use codespan_reporting::term::{termcolor::Color, Chars, Config, DisplayStyle, Styles};
4 
5 mod support;
6 
7 use self::support::TestData;
8 
9 lazy_static::lazy_static! {
10     static ref TEST_CONFIG: Config = Config {
11         // Always use blue so tests are consistent across platforms
12         styles: Styles::with_blue(Color::Blue),
13         ..Config::default()
14     };
15 }
16 
17 macro_rules! test_emit {
18     (rich_color) => {
19         #[test]
20         fn rich_color() {
21             let config = Config {
22                 display_style: DisplayStyle::Rich,
23                 ..TEST_CONFIG.clone()
24             };
25 
26             insta::assert_snapshot!(TEST_DATA.emit_color(&config));
27         }
28     };
29     (medium_color) => {
30         #[test]
31         fn medium_color() {
32             let config = Config {
33                 display_style: DisplayStyle::Medium,
34                 ..TEST_CONFIG.clone()
35             };
36 
37             insta::assert_snapshot!(TEST_DATA.emit_color(&config));
38         }
39     };
40     (short_color) => {
41         #[test]
42         fn short_color() {
43             let config = Config {
44                 display_style: DisplayStyle::Short,
45                 ..TEST_CONFIG.clone()
46             };
47 
48             insta::assert_snapshot!(TEST_DATA.emit_color(&config));
49         }
50     };
51     (rich_no_color) => {
52         #[test]
53         fn rich_no_color() {
54             let config = Config {
55                 display_style: DisplayStyle::Rich,
56                 ..TEST_CONFIG.clone()
57             };
58 
59             insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
60         }
61     };
62     (medium_no_color) => {
63         #[test]
64         fn medium_no_color() {
65             let config = Config {
66                 display_style: DisplayStyle::Medium,
67                 ..TEST_CONFIG.clone()
68             };
69 
70             insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
71         }
72     };
73     (short_no_color) => {
74         #[test]
75         fn short_no_color() {
76             let config = Config {
77                 display_style: DisplayStyle::Short,
78                 ..TEST_CONFIG.clone()
79             };
80 
81             insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
82         }
83     };
84     (rich_ascii_no_color) => {
85         #[test]
86         fn rich_ascii_no_color() {
87             let config = Config {
88                 display_style: DisplayStyle::Rich,
89                 chars: Chars::ascii(),
90                 ..TEST_CONFIG.clone()
91             };
92 
93             insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
94         }
95     };
96 }
97 
98 mod empty {
99     use super::*;
100 
101     lazy_static::lazy_static! {
102         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, &'static str>> = {
103             let files = SimpleFiles::new();
104 
105             let diagnostics = vec![
106                 Diagnostic::bug(),
107                 Diagnostic::error(),
108                 Diagnostic::warning(),
109                 Diagnostic::note(),
110                 Diagnostic::help(),
111                 Diagnostic::bug(),
112             ];
113 
114             TestData { files, diagnostics }
115         };
116     }
117 
118     test_emit!(rich_color);
119     test_emit!(medium_color);
120     test_emit!(short_color);
121     test_emit!(rich_no_color);
122     test_emit!(medium_no_color);
123     test_emit!(short_no_color);
124     test_emit!(rich_ascii_no_color);
125 }
126 
127 /// Based on:
128 /// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/codemap_tests/one_line.stderr
129 mod same_line {
130     use super::*;
131 
132     lazy_static::lazy_static! {
133         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
134             let mut files = SimpleFiles::new();
135 
136             let file_id1 = files.add(
137                 "one_line.rs",
138                 unindent::unindent(r#"
139                     fn main() {
140                         let mut v = vec![Some("foo"), Some("bar")];
141                         v.push(v.pop().unwrap());
142                     }
143                 "#),
144             );
145 
146             let diagnostics = vec![
147                 Diagnostic::error()
148                     .with_code("E0499")
149                     .with_message("cannot borrow `v` as mutable more than once at a time")
150                     .with_labels(vec![
151                         Label::primary(file_id1, 71..72)
152                             .with_message("second mutable borrow occurs here"),
153                         Label::secondary(file_id1, 64..65)
154                             .with_message("first borrow later used by call"),
155                         Label::secondary(file_id1, 66..70)
156                             .with_message("first mutable borrow occurs here"),
157                     ]),
158                 Diagnostic::error()
159                     .with_message("aborting due to previous error")
160                     .with_notes(vec![
161                         "For more information about this error, try `rustc --explain E0499`.".to_owned(),
162                     ]),
163             ];
164 
165             TestData { files, diagnostics }
166         };
167     }
168 
169     test_emit!(rich_color);
170     test_emit!(medium_color);
171     test_emit!(short_color);
172     test_emit!(rich_no_color);
173     test_emit!(medium_no_color);
174     test_emit!(short_no_color);
175     test_emit!(rich_ascii_no_color);
176 }
177 
178 /// Based on:
179 /// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/nested_impl_trait.stderr
180 /// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/typeck/typeck_type_placeholder_item.stderr
181 /// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/no_send_res_ports.stderr
182 mod overlapping {
183     use super::*;
184 
185     lazy_static::lazy_static! {
186         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
187             let mut files = SimpleFiles::new();
188 
189             let file_id1 = files.add(
190                 "nested_impl_trait.rs",
191                 unindent::unindent(r#"
192                     use std::fmt::Debug;
193 
194                     fn fine(x: impl Into<u32>) -> impl Into<u32> { x }
195 
196                     fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
197                 "#),
198             );
199             let file_id2 = files.add(
200                 "typeck_type_placeholder_item.rs",
201                 unindent::unindent(r#"
202                     fn fn_test1() -> _ { 5 }
203                     fn fn_test2(x: i32) -> (_, _) { (x, x) }
204                 "#),
205             );
206             let file_id3 = files.add(
207                 "libstd/thread/mod.rs",
208                 unindent::unindent(r#"
209                     #[stable(feature = "rust1", since = "1.0.0")]
210                     pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>>
211                     where
212                         F: FnOnce() -> T,
213                         F: Send + 'static,
214                         T: Send + 'static,
215                     {
216                         unsafe { self.spawn_unchecked(f) }
217                     }
218                 "#),
219             );
220             let file_id4 = files.add(
221                 "no_send_res_ports.rs",
222                 unindent::unindent(r#"
223                     use std::thread;
224                     use std::rc::Rc;
225 
226                     #[derive(Debug)]
227                     struct Port<T>(Rc<T>);
228 
229                     fn main() {
230                         #[derive(Debug)]
231                         struct Foo {
232                             _x: Port<()>,
233                         }
234 
235                         impl Drop for Foo {
236                             fn drop(&mut self) {}
237                         }
238 
239                         fn foo(x: Port<()>) -> Foo {
240                             Foo {
241                                 _x: x
242                             }
243                         }
244 
245                         let x = foo(Port(Rc::new(())));
246 
247                         thread::spawn(move|| {
248                             let y = x;
249                             println!("{:?}", y);
250                         });
251                     }
252                 "#),
253             );
254 
255             let diagnostics = vec![
256                 Diagnostic::error()
257                     .with_code("E0666")
258                     .with_message("nested `impl Trait` is not allowed")
259                     .with_labels(vec![
260                         Label::primary(file_id1, 129..139)
261                             .with_message("nested `impl Trait` here"),
262                         Label::secondary(file_id1, 119..140)
263                             .with_message("outer `impl Trait`"),
264                     ]),
265                 Diagnostic::error()
266                     .with_code("E0121")
267                     .with_message("the type placeholder `_` is not allowed within types on item signatures")
268                         .with_labels(vec![
269                             Label::primary(file_id2, 17..18)
270                                 .with_message("not allowed in type signatures"),
271                             Label::secondary(file_id2, 17..18)
272                                 .with_message("help: replace with the correct return type: `i32`"),
273                         ]),
274                 Diagnostic::error()
275                     .with_code("E0121")
276                     .with_message("the type placeholder `_` is not allowed within types on item signatures")
277                         .with_labels(vec![
278                             Label::primary(file_id2, 49..50)
279                                 .with_message("not allowed in type signatures"),
280                             Label::primary(file_id2, 52..53)
281                                 .with_message("not allowed in type signatures"),
282                             Label::secondary(file_id2, 48..54)
283                                 .with_message("help: replace with the correct return type: `(i32, i32)`"),
284                         ]),
285                 Diagnostic::error()
286                     .with_code("E0277")
287                     .with_message("`std::rc::Rc<()>` cannot be sent between threads safely")
288                     .with_labels(vec![
289                         Label::primary(file_id4, 339..352)
290                             .with_message("`std::rc::Rc<()>` cannot be sent between threads safely"),
291                         Label::secondary(file_id4, 353..416)
292                             .with_message("within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`"),
293                         Label::secondary(file_id3, 141..145)
294                             .with_message("required by this bound in `std::thread::spawn`"),
295                     ])
296                     .with_notes(vec![
297                         "help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`".to_owned(),
298                         "note: required because it appears within the type `Port<()>`".to_owned(),
299                         "note: required because it appears within the type `main::Foo`".to_owned(),
300                         "note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`".to_owned(),
301                     ]),
302                 Diagnostic::error()
303                     .with_message("aborting due 5 previous errors")
304                     .with_notes(vec![
305                         "Some errors have detailed explanations: E0121, E0277, E0666.".to_owned(),
306                         "For more information about an error, try `rustc --explain E0121`.".to_owned(),
307                     ]),
308             ];
309 
310             TestData { files, diagnostics }
311         };
312     }
313 
314     test_emit!(rich_color);
315     test_emit!(medium_color);
316     test_emit!(short_color);
317     test_emit!(rich_no_color);
318     test_emit!(medium_no_color);
319     test_emit!(short_no_color);
320     test_emit!(rich_ascii_no_color);
321 }
322 
323 mod message {
324     use super::*;
325 
326     lazy_static::lazy_static! {
327         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, &'static str>> = {
328             let files = SimpleFiles::new();
329 
330             let diagnostics = vec![
331                 Diagnostic::error().with_message("a message"),
332                 Diagnostic::warning().with_message("a message"),
333                 Diagnostic::note().with_message("a message"),
334                 Diagnostic::help().with_message("a message"),
335             ];
336 
337             TestData { files, diagnostics }
338         };
339     }
340 
341     test_emit!(rich_color);
342     test_emit!(medium_color);
343     test_emit!(short_color);
344     test_emit!(rich_no_color);
345     test_emit!(medium_no_color);
346     test_emit!(short_no_color);
347     test_emit!(rich_ascii_no_color);
348 }
349 
350 mod message_and_notes {
351     use super::*;
352 
353     lazy_static::lazy_static! {
354         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, &'static str>> = {
355             let files = SimpleFiles::new();
356 
357             let diagnostics = vec![
358                 Diagnostic::error().with_message("a message").with_notes(vec!["a note".to_owned()]),
359                 Diagnostic::warning().with_message("a message").with_notes(vec!["a note".to_owned()]),
360                 Diagnostic::note().with_message("a message").with_notes(vec!["a note".to_owned()]),
361                 Diagnostic::help().with_message("a message").with_notes(vec!["a note".to_owned()]),
362             ];
363 
364             TestData { files, diagnostics }
365         };
366     }
367 
368     test_emit!(rich_color);
369     test_emit!(medium_color);
370     test_emit!(short_color);
371     test_emit!(rich_no_color);
372     test_emit!(medium_no_color);
373     test_emit!(short_no_color);
374     test_emit!(rich_ascii_no_color);
375 }
376 
377 mod message_errorcode {
378     use super::*;
379 
380     lazy_static::lazy_static! {
381         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, &'static str>> = {
382             let files = SimpleFiles::new();
383 
384             let diagnostics = vec![
385                 Diagnostic::error().with_message("a message").with_code("E0001"),
386                 Diagnostic::warning().with_message("a message").with_code("W001"),
387                 Diagnostic::note().with_message("a message").with_code("N0815"),
388                 Diagnostic::help().with_message("a message").with_code("H4711"),
389                 Diagnostic::error().with_message("where did my errorcode go?").with_code(""),
390                 Diagnostic::warning().with_message("where did my errorcode go?").with_code(""),
391                 Diagnostic::note().with_message("where did my errorcode go?").with_code(""),
392                 Diagnostic::help().with_message("where did my errorcode go?").with_code(""),
393             ];
394 
395             TestData { files, diagnostics }
396         };
397     }
398 
399     test_emit!(rich_no_color);
400     test_emit!(short_no_color);
401     test_emit!(rich_ascii_no_color);
402 }
403 
404 mod empty_ranges {
405     use super::*;
406 
407     lazy_static::lazy_static! {
408         static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, &'static str>> = {
409             let file = SimpleFile::new("hello", "Hello world!\nBye world!\n   ");
410             let eof = file.source().len();
411 
412             let diagnostics = vec![
413                 Diagnostic::note()
414                     .with_message("middle")
415                     .with_labels(vec![Label::primary((), 6..6).with_message("middle")]),
416                 Diagnostic::note()
417                     .with_message("end of line")
418                     .with_labels(vec![Label::primary((), 12..12).with_message("end of line")]),
419                 Diagnostic::note()
420                     .with_message("end of line")
421                     .with_labels(vec![Label::primary((), 23..23).with_message("end of line")]),
422                 Diagnostic::note()
423                     .with_message("end of file")
424                     .with_labels(vec![Label::primary((), eof..eof).with_message("end of file")]),
425             ];
426 
427             TestData { files: file, diagnostics }
428         };
429     }
430 
431     test_emit!(rich_color);
432     test_emit!(medium_color);
433     test_emit!(short_color);
434     test_emit!(rich_no_color);
435     test_emit!(medium_no_color);
436     test_emit!(short_no_color);
437     test_emit!(rich_ascii_no_color);
438 }
439 
440 mod same_ranges {
441     use super::*;
442 
443     lazy_static::lazy_static! {
444         static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, &'static str>> = {
445             let file = SimpleFile::new("same_range", "::S { }");
446 
447             let diagnostics = vec![
448                 Diagnostic::error()
449                     .with_message("Unexpected token")
450                     .with_labels(vec![
451                         Label::primary((), 4..4).with_message("Unexpected '{'"),
452                         Label::secondary((), 4..4).with_message("Expected '('"),
453                     ]),
454             ];
455 
456             TestData { files: file, diagnostics }
457         };
458     }
459 
460     test_emit!(rich_color);
461     test_emit!(medium_color);
462     test_emit!(short_color);
463     test_emit!(rich_no_color);
464     test_emit!(medium_no_color);
465     test_emit!(short_no_color);
466     test_emit!(rich_ascii_no_color);
467 }
468 
469 mod multifile {
470     use super::*;
471 
472     lazy_static::lazy_static! {
473         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
474             let mut files = SimpleFiles::new();
475 
476             let file_id1 = files.add(
477                 "Data/Nat.fun",
478                 unindent::unindent(
479                     "
480                         module Data.Nat where
481 
482                         data Nat : Type where
483                             zero : Nat
484                             succ : Nat → Nat
485 
486                         {-# BUILTIN NATRAL Nat #-}
487 
488                         infixl 6 _+_ _-_
489 
490                         _+_ : Nat → Nat → Nat
491                         zero    + n₂ = n₂
492                         succ n₁ + n₂ = succ (n₁ + n₂)
493 
494                         _-_ : Nat → Nat → Nat
495                         n₁      - zero    = n₁
496                         zero    - succ n₂ = zero
497                         succ n₁ - succ n₂ = n₁ - n₂
498                     ",
499                 ),
500             );
501 
502             let file_id2 = files.add(
503                 "Test.fun",
504                 unindent::unindent(
505                     r#"
506                         module Test where
507 
508                         _ : Nat
509                         _ = 123 + "hello"
510                     "#,
511                 ),
512             );
513 
514             let diagnostics = vec![
515                 // Unknown builtin error
516                 Diagnostic::error()
517                     .with_message("unknown builtin: `NATRAL`")
518                     .with_labels(vec![Label::primary(file_id1, 96..102).with_message("unknown builtin")])
519                     .with_notes(vec![
520                         "there is a builtin with a similar name: `NATURAL`".to_owned(),
521                     ]),
522                 // Unused parameter warning
523                 Diagnostic::warning()
524                     .with_message("unused parameter pattern: `n₂`")
525                     .with_labels(vec![Label::primary(file_id1, 285..289).with_message("unused parameter")])
526                     .with_notes(vec!["consider using a wildcard pattern: `_`".to_owned()]),
527                 // Unexpected type error
528                 Diagnostic::error()
529                     .with_message("unexpected type in application of `_+_`")
530                     .with_code("E0001")
531                     .with_labels(vec![
532                         Label::primary(file_id2, 37..44).with_message("expected `Nat`, found `String`"),
533                         Label::secondary(file_id1, 130..155).with_message("based on the definition of `_+_`"),
534                     ])
535                     .with_notes(vec![unindent::unindent(
536                         "
537                             expected type `Nat`
538                                found type `String`
539                         ",
540                     )]),
541             ];
542 
543             TestData { files, diagnostics }
544         };
545     }
546 
547     test_emit!(rich_color);
548     test_emit!(medium_color);
549     test_emit!(short_color);
550     test_emit!(rich_no_color);
551     test_emit!(medium_no_color);
552     test_emit!(short_no_color);
553     test_emit!(rich_ascii_no_color);
554 }
555 
556 mod fizz_buzz {
557     use super::*;
558 
559     lazy_static::lazy_static! {
560         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
561             let mut files = SimpleFiles::new();
562 
563             let file_id = files.add(
564                 "FizzBuzz.fun",
565                 unindent::unindent(
566                     r#"
567                         module FizzBuzz where
568 
569                         fizz₁ : Nat → String
570                         fizz₁ num = case (mod num 5) (mod num 3) of
571                             0 0 => "FizzBuzz"
572                             0 _ => "Fizz"
573                             _ 0 => "Buzz"
574                             _ _ => num
575 
576                         fizz₂ : Nat → String
577                         fizz₂ num =
578                             case (mod num 5) (mod num 3) of
579                                 0 0 => "FizzBuzz"
580                                 0 _ => "Fizz"
581                                 _ 0 => "Buzz"
582                                 _ _ => num
583                     "#,
584                 ),
585             );
586 
587             let diagnostics = vec![
588                 // Incompatible match clause error
589                 Diagnostic::error()
590                     .with_message("`case` clauses have incompatible types")
591                     .with_code("E0308")
592                     .with_labels(vec![
593                         Label::primary(file_id, 163..166).with_message("expected `String`, found `Nat`"),
594                         Label::secondary(file_id, 62..166).with_message("`case` clauses have incompatible types"),
595                         Label::secondary(file_id, 41..47).with_message("expected type `String` found here"),
596                     ])
597                     .with_notes(vec![unindent::unindent(
598                         "
599                             expected type `String`
600                                found type `Nat`
601                         ",
602                     )]),
603                 // Incompatible match clause error
604                 Diagnostic::error()
605                     .with_message("`case` clauses have incompatible types")
606                     .with_code("E0308")
607                     .with_labels(vec![
608                         Label::primary(file_id, 328..331).with_message("expected `String`, found `Nat`"),
609                         Label::secondary(file_id, 211..331).with_message("`case` clauses have incompatible types"),
610                         Label::secondary(file_id, 258..268).with_message("this is found to be of type `String`"),
611                         Label::secondary(file_id, 284..290).with_message("this is found to be of type `String`"),
612                         Label::secondary(file_id, 306..312).with_message("this is found to be of type `String`"),
613                         Label::secondary(file_id, 186..192).with_message("expected type `String` found here"),
614                     ])
615                     .with_notes(vec![unindent::unindent(
616                         "
617                             expected type `String`
618                                found type `Nat`
619                         ",
620                     )]),
621             ];
622 
623             TestData { files, diagnostics }
624         };
625     }
626 
627     test_emit!(rich_color);
628     test_emit!(medium_color);
629     test_emit!(short_color);
630     test_emit!(rich_no_color);
631     test_emit!(medium_no_color);
632     test_emit!(short_no_color);
633     test_emit!(rich_ascii_no_color);
634 }
635 
636 mod multiline_overlapping {
637     use super::*;
638 
639     lazy_static::lazy_static! {
640         static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, String>> = {
641             let file = SimpleFile::new(
642                 "codespan/src/file.rs",
643                 [
644                     "        match line_index.compare(self.last_line_index()) {",
645                     "            Ordering::Less => Ok(self.line_starts()[line_index.to_usize()]),",
646                     "            Ordering::Equal => Ok(self.source_span().end()),",
647                     "            Ordering::Greater => LineIndexOutOfBoundsError {",
648                     "                given: line_index,",
649                     "                max: self.last_line_index(),",
650                     "            },",
651                     "        }",
652                 ].join("\n"),
653             );
654 
655             let diagnostics = vec![
656                 Diagnostic::error()
657                     .with_message("match arms have incompatible types")
658                     .with_code("E0308")
659                     .with_labels(vec![
660                         // this secondary label is before the primary label to test the locus calculation (see issue #259)
661                         Label::secondary((), 89..134).with_message("this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`"),
662                         Label::primary((), 230..351).with_message("expected enum `Result`, found struct `LineIndexOutOfBoundsError`"),
663                         Label::secondary((), 8..362).with_message("`match` arms have incompatible types"),
664                         Label::secondary((), 167..195).with_message("this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`"),
665                     ])
666                     .with_notes(vec![unindent::unindent(
667                         "
668                             expected type `Result<ByteIndex, LineIndexOutOfBoundsError>`
669                                found type `LineIndexOutOfBoundsError`
670                         ",
671                     )]),
672             ];
673 
674             TestData { files: file, diagnostics }
675         };
676     }
677 
678     test_emit!(rich_color);
679     test_emit!(medium_color);
680     test_emit!(short_color);
681     test_emit!(rich_no_color);
682     test_emit!(medium_no_color);
683     test_emit!(short_no_color);
684     test_emit!(rich_ascii_no_color);
685 }
686 
687 mod tabbed {
688     use super::*;
689 
690     lazy_static::lazy_static! {
691         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
692             let mut files = SimpleFiles::new();
693 
694             let file_id = files.add(
695                 "tabbed",
696                 [
697                     "Entity:",
698                     "\tArmament:",
699                     "\t\tWeapon: DogJaw",
700                     "\t\tReloadingCondition:\tattack-cooldown",
701                     "\tFoo: Bar",
702                 ]
703                 .join("\n"),
704             );
705 
706             let diagnostics = vec![
707                 Diagnostic::warning()
708                     .with_message("unknown weapon `DogJaw`")
709                     .with_labels(vec![Label::primary(file_id, 29..35).with_message("the weapon")]),
710                 Diagnostic::warning()
711                     .with_message("unknown condition `attack-cooldown`")
712                     .with_labels(vec![Label::primary(file_id, 58..73).with_message("the condition")]),
713                 Diagnostic::warning()
714                     .with_message("unknown field `Foo`")
715                     .with_labels(vec![Label::primary(file_id, 75..78).with_message("the field")]),
716             ];
717 
718             TestData { files, diagnostics }
719         };
720     }
721 
722     #[test]
tab_width_default_no_color()723     fn tab_width_default_no_color() {
724         let config = TEST_CONFIG.clone();
725 
726         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
727     }
728 
729     #[test]
tab_width_3_no_color()730     fn tab_width_3_no_color() {
731         let config = Config {
732             tab_width: 3,
733             ..TEST_CONFIG.clone()
734         };
735 
736         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
737     }
738 
739     #[test]
tab_width_6_no_color()740     fn tab_width_6_no_color() {
741         let config = Config {
742             tab_width: 6,
743             ..TEST_CONFIG.clone()
744         };
745 
746         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
747     }
748 }
749 
750 mod tab_columns {
751     use super::*;
752 
753     lazy_static::lazy_static! {
754         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
755             let mut files = SimpleFiles::new();
756 
757             let source = [
758                 "\thello",
759                 "∙\thello",
760                 "∙∙\thello",
761                 "∙∙∙\thello",
762                 "∙∙∙∙\thello",
763                 "∙∙∙∙∙\thello",
764                 "∙∙∙∙∙∙\thello",
765             ].join("\n");
766             let hello_ranges = source
767                 .match_indices("hello")
768                 .map(|(start, hello)| start..(start+hello.len()))
769                 .collect::<Vec<_>>();
770 
771             let file_id = files.add("tab_columns", source);
772 
773             let diagnostics = vec![
774                 Diagnostic::warning()
775                     .with_message("tab test")
776                     .with_labels(
777                         hello_ranges
778                             .into_iter()
779                             .map(|range| Label::primary(file_id, range))
780                             .collect(),
781                     ),
782             ];
783 
784             TestData { files, diagnostics }
785         };
786     }
787 
788     #[test]
tab_width_default_no_color()789     fn tab_width_default_no_color() {
790         let config = TEST_CONFIG.clone();
791 
792         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
793     }
794 
795     #[test]
tab_width_2_no_color()796     fn tab_width_2_no_color() {
797         let config = Config {
798             tab_width: 2,
799             ..TEST_CONFIG.clone()
800         };
801 
802         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
803     }
804 
805     #[test]
tab_width_3_no_color()806     fn tab_width_3_no_color() {
807         let config = Config {
808             tab_width: 3,
809             ..TEST_CONFIG.clone()
810         };
811 
812         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
813     }
814 
815     #[test]
tab_width_6_no_color()816     fn tab_width_6_no_color() {
817         let config = Config {
818             tab_width: 6,
819             ..TEST_CONFIG.clone()
820         };
821 
822         insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
823     }
824 }
825 
826 /// Based on:
827 /// - https://github.com/TheSamsa/rust/blob/75cf41afb468152611212271bae026948cd3ba46/src/test/ui/codemap_tests/unicode.stderr
828 mod unicode {
829     use super::*;
830 
831     lazy_static::lazy_static! {
832         static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, String>> = {
833             let prefix = r#"extern "#;
834             let abi = r#""路濫狼á́́""#;
835             let suffix = r#" fn foo() {}"#;
836 
837             let file = SimpleFile::new(
838                 "unicode.rs",
839                 format!("{}{}{}", prefix, abi, suffix),
840             );
841 
842             let diagnostics = vec![
843                 Diagnostic::error()
844                     .with_code("E0703")
845                     .with_message("invalid ABI: found `路濫狼á́́`")
846                     .with_labels(vec![
847                         Label::primary((), prefix.len()..(prefix.len() + abi.len()))
848                             .with_message("invalid ABI"),
849                     ])
850                     .with_notes(vec![unindent::unindent(
851                         "
852                             valid ABIs:
853                               - aapcs
854                               - amdgpu-kernel
855                               - C
856                               - cdecl
857                               - efiapi
858                               - fastcall
859                               - msp430-interrupt
860                               - platform-intrinsic
861                               - ptx-kernel
862                               - Rust
863                               - rust-call
864                               - rust-intrinsic
865                               - stdcall
866                               - system
867                               - sysv64
868                               - thiscall
869                               - unadjusted
870                               - vectorcall
871                               - win64
872                               - x86-interrupt
873                         ",
874                     )]),
875                 Diagnostic::error()
876                     .with_message("aborting due to previous error")
877                     .with_notes(vec![
878                         "For more information about this error, try `rustc --explain E0703`.".to_owned(),
879                     ]),
880             ];
881 
882             TestData { files: file, diagnostics }
883         };
884     }
885 
886     test_emit!(rich_no_color);
887     test_emit!(medium_no_color);
888     test_emit!(short_no_color);
889 }
890 
891 mod unicode_spans {
892     use super::*;
893 
894     lazy_static::lazy_static! {
895         static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, String>> = {
896             let moon_phases = format!("{}", r#"����������������������������������"#);
897             let invalid_start = 1;
898             let invalid_end = "��".len() - 1;
899             assert_eq!(moon_phases.is_char_boundary(invalid_start), false);
900             assert_eq!(moon_phases.is_char_boundary(invalid_end), false);
901             assert_eq!("��".len(), 4);
902             let file = SimpleFile::new(
903                 "moon_jump.rs",
904                 moon_phases,
905             );
906             let diagnostics = vec![
907                 Diagnostic::error()
908                     .with_code("E01")
909                     .with_message("cow may not jump during new moon.")
910                     .with_labels(vec![
911                         Label::primary((), invalid_start..invalid_end)
912                             .with_message("Invalid jump"),
913                     ]),
914                 Diagnostic::note()
915                     .with_message("invalid unicode range")
916                     .with_labels(vec![
917                         Label::secondary((), invalid_start.."��".len())
918                             .with_message("Cow range does not start at boundary."),
919                     ]),
920                 Diagnostic::note()
921                     .with_message("invalid unicode range")
922                     .with_labels(vec![
923                         Label::secondary((), "����".len().."������".len() - 1)
924                             .with_message("Cow range does not end at boundary."),
925                     ]),
926                 Diagnostic::note()
927                     .with_message("invalid unicode range")
928                     .with_labels(vec![
929                         Label::secondary((), invalid_start.."������".len() - 1)
930                             .with_message("Cow does not start or end at boundary."),
931                     ]),
932             ];
933             TestData{files: file, diagnostics }
934         };
935     }
936 
937     test_emit!(rich_no_color);
938     test_emit!(medium_no_color);
939     test_emit!(short_no_color);
940 }
941 
942 mod position_indicator {
943     use super::*;
944 
945     lazy_static::lazy_static! {
946         static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, String>> = {
947             let file = SimpleFile::new(
948                 "tests/main.js",
949                 [
950                     "\"use strict\";",
951                     "let zero=0;",
952                     "function foo() {",
953                     "  \"use strict\";",
954                     "  one=1;",
955                     "}",
956                 ].join("\n"),
957             );
958             let diagnostics = vec![
959                 Diagnostic::warning()
960                     .with_code("ParserWarning")
961                     .with_message("The strict mode declaration in the body of function `foo` is redundant, as the outer scope is already in strict mode")
962                     .with_labels(vec![
963                         Label::primary((), 45..57)
964                             .with_message("This strict mode declaration is redundant"),
965                         Label::secondary((), 0..12)
966                             .with_message("Strict mode is first declared here"),
967                     ]),
968             ];
969             TestData{files: file, diagnostics }
970         };
971     }
972 
973     test_emit!(rich_no_color);
974     test_emit!(medium_no_color);
975     test_emit!(short_no_color);
976     test_emit!(rich_ascii_no_color);
977 }
978 
979 mod multiline_omit {
980     use super::*;
981 
982     lazy_static::lazy_static! {
983         static ref TEST_CONFIG: Config = Config {
984             styles: Styles::with_blue(Color::Blue),
985             start_context_lines: 2,
986             end_context_lines: 1,
987             ..Config::default()
988         };
989 
990         static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
991             let mut files = SimpleFiles::new();
992 
993             let file_id1 = files.add(
994                 "empty_if_comments.lua",
995                 [
996                     "elseif 3 then", // primary label starts here
997                     "",              // context line
998                     "",
999                     "",
1000                     "",
1001                     "",
1002                     "",
1003                     "",
1004                     "",     // context line
1005                     "else", // primary label ends here
1006                 ]
1007                 .join("\n"),
1008             );
1009 
1010             let file_id2 = files.add(
1011                 "src/lib.rs",
1012                 [
1013                     "fn main() {",
1014                     "    1",   // primary label starts here
1015                     "    + 1", // context line
1016                     "    + 1", // skip
1017                     "    + 1", // skip
1018                     "    + 1", // skip
1019                     "    +1",  // secondary label here
1020                     "    + 1", // this single line will not be skipped; the previously filtered out label must be retrieved
1021                     "    + 1", // context line
1022                     "    + 1", // primary label ends here
1023                     "}",
1024                 ]
1025                 .join("\n"),
1026             );
1027 
1028             let diagnostics = vec![
1029                 Diagnostic::error()
1030                     .with_message("empty elseif block")
1031                     .with_code("empty_if")
1032                     .with_labels(vec![
1033                         Label::primary(file_id1, 0..23),
1034                         Label::secondary(file_id1, 15..21).with_message("content should be in here"),
1035                     ]),
1036                 Diagnostic::error()
1037                     .with_message("mismatched types")
1038                     .with_code("E0308")
1039                     .with_labels(vec![
1040                         Label::primary(file_id2, 17..80).with_message("expected (), found integer"),
1041                         Label::secondary(file_id2, 55..55).with_message("missing whitespace"),
1042                     ])
1043                     .with_notes(vec![
1044                         "note:\texpected type `()`\n\tfound type `{integer}`".to_owned()
1045                     ]),
1046             ];
1047 
1048             TestData { files, diagnostics }
1049         };
1050     }
1051 
1052     test_emit!(rich_no_color);
1053 }
1054