]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/syntax_highlighting/tests.rs
add test
[rust.git] / crates / ide / src / syntax_highlighting / tests.rs
1 use std::time::Instant;
2
3 use expect_test::{expect_file, ExpectFile};
4 use ide_db::SymbolKind;
5 use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear};
6
7 use crate::{fixture, FileRange, HlTag, TextRange};
8
9 #[test]
10 fn test_highlighting() {
11     check_highlighting(
12         r#"
13 //- proc_macros: identity, mirror
14 //- minicore: derive, copy
15 //- /main.rs crate:main deps:foo
16 use inner::{self as inner_mod};
17 mod inner {}
18
19 #[proc_macros::identity]
20 pub mod ops {
21     #[lang = "fn_once"]
22     pub trait FnOnce<Args> {}
23
24     #[lang = "fn_mut"]
25     pub trait FnMut<Args>: FnOnce<Args> {}
26
27     #[lang = "fn"]
28     pub trait Fn<Args>: FnMut<Args> {}
29 }
30
31 proc_macros::mirror! {
32     {
33         ,i32 :x pub
34         ,i32 :y pub
35     } Foo struct
36 }
37
38 trait Bar where Self: {
39     fn bar(&self) -> i32;
40 }
41
42 impl Bar for Foo where Self: {
43     fn bar(&self) -> i32 {
44         self.x
45     }
46 }
47
48 impl Foo {
49     fn baz(mut self, f: Foo) -> i32 {
50         f.baz(self)
51     }
52
53     fn qux(&mut self) {
54         self.x = 0;
55     }
56
57     fn quop(&self) -> i32 {
58         self.x
59     }
60 }
61
62 #[derive(Copy)]
63 struct FooCopy {
64     x: u32,
65 }
66
67 impl FooCopy {
68     fn baz(self, f: FooCopy) -> u32 {
69         f.baz(self)
70     }
71
72     fn qux(&mut self) {
73         self.x = 0;
74     }
75
76     fn quop(&self) -> u32 {
77         self.x
78     }
79 }
80
81 fn str() {
82     str();
83 }
84
85 fn foo<'a, T>() -> T {
86     foo::<'a, i32>()
87 }
88
89 fn never() -> ! {
90     loop {}
91 }
92
93 fn const_param<const FOO: usize>() -> usize {
94     FOO
95 }
96
97 use ops::Fn;
98 fn baz<F: Fn() -> ()>(f: F) {
99     f()
100 }
101
102 fn foobar() -> impl Copy {}
103
104 fn foo() {
105     let bar = foobar();
106 }
107
108 macro_rules! def_fn {
109     ($($tt:tt)*) => {$($tt)*}
110 }
111
112 def_fn! {
113     fn bar() -> u32 {
114         100
115     }
116 }
117
118 macro_rules! dont_color_me_braces {
119     () => {0}
120 }
121
122 macro_rules! noop {
123     ($expr:expr) => {
124         $expr
125     }
126 }
127
128 macro_rules! keyword_frag {
129     ($type:ty) => ($type)
130 }
131
132 macro with_args($i:ident) {
133     $i
134 }
135
136 macro without_args {
137     ($i:ident) => {
138         $i
139     }
140 }
141
142 // comment
143 fn main() {
144     println!("Hello, {}!", 92);
145     dont_color_me_braces!();
146
147     let mut vec = Vec::new();
148     if true {
149         let x = 92;
150         vec.push(Foo { x, y: 1 });
151     }
152
153     for e in vec {
154         // Do nothing
155     }
156
157     noop!(noop!(1));
158
159     let mut x = 42;
160     x += 1;
161     let y = &mut x;
162     let z = &y;
163
164     let Foo { x: z, y } = Foo { x: z, y };
165
166     y;
167
168     let mut foo = Foo { x, y: x };
169     let foo2 = Foo { x, y: x };
170     foo.quop();
171     foo.qux();
172     foo.baz(foo2);
173
174     let mut copy = FooCopy { x };
175     copy.quop();
176     copy.qux();
177     copy.baz(copy);
178
179     let a = |x| x;
180     let bar = Foo::baz;
181
182     let baz = (-42,);
183     let baz = -baz.0;
184
185     let _ = !true;
186
187     'foo: loop {
188         break 'foo;
189         continue 'foo;
190     }
191 }
192
193 enum Option<T> {
194     Some(T),
195     None,
196 }
197 use Option::*;
198
199 impl<T> Option<T> {
200     fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
201         match other {
202             None => unimplemented!(),
203             Nope => Nope,
204         }
205     }
206 }
207
208 async fn learn_and_sing() {
209     let song = learn_song().await;
210     sing_song(song).await;
211 }
212
213 async fn async_main() {
214     let f1 = learn_and_sing();
215     let f2 = dance();
216     futures::join!(f1, f2);
217 }
218
219 fn use_foo_items() {
220     let bob = foo::Person {
221         name: "Bob",
222         age: foo::consts::NUMBER,
223     };
224
225     let control_flow = foo::identity(foo::ControlFlow::Continue);
226
227     if control_flow.should_die() {
228         foo::die!();
229     }
230 }
231
232 pub enum Bool { True, False }
233
234 impl Bool {
235     pub const fn to_primitive(self) -> bool {
236         matches!(self, Self::True)
237     }
238 }
239 const USAGE_OF_BOOL:bool = Bool::True.to_primitive();
240
241 trait Baz {
242     type Qux;
243 }
244
245 fn baz<T>(t: T)
246 where
247     T: Baz,
248     <T as Baz>::Qux: Bar {}
249
250 //- /foo.rs crate:foo
251 pub struct Person {
252     pub name: &'static str,
253     pub age: u8,
254 }
255
256 pub enum ControlFlow {
257     Continue,
258     Die,
259 }
260
261 impl ControlFlow {
262     pub fn should_die(self) -> bool {
263         matches!(self, ControlFlow::Die)
264     }
265 }
266
267 pub fn identity<T>(x: T) -> T { x }
268
269 pub mod consts {
270     pub const NUMBER: i64 = 92;
271 }
272
273 macro_rules! die {
274     () => {
275         panic!();
276     };
277 }
278 "#
279         .trim(),
280         expect_file!["./test_data/highlighting.html"],
281         false,
282     );
283 }
284
285 #[test]
286 fn test_rainbow_highlighting() {
287     check_highlighting(
288         r#"
289 fn main() {
290     let hello = "hello";
291     let x = hello.to_string();
292     let y = hello.to_string();
293
294     let x = "other color please!";
295     let y = x.to_string();
296 }
297
298 fn bar() {
299     let mut hello = "hello";
300 }
301 "#
302         .trim(),
303         expect_file!["./test_data/rainbow_highlighting.html"],
304         true,
305     );
306 }
307
308 #[test]
309 fn benchmark_syntax_highlighting_long_struct() {
310     if skip_slow_tests() {
311         return;
312     }
313
314     let fixture = bench_fixture::big_struct();
315     let (analysis, file_id) = fixture::file(&fixture);
316
317     let hash = {
318         let _pt = bench("syntax highlighting long struct");
319         analysis
320             .highlight(file_id)
321             .unwrap()
322             .iter()
323             .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
324             .count()
325     };
326     assert_eq!(hash, 2001);
327 }
328
329 #[test]
330 fn syntax_highlighting_not_quadratic() {
331     if skip_slow_tests() {
332         return;
333     }
334
335     let mut al = AssertLinear::default();
336     while al.next_round() {
337         for i in 6..=10 {
338             let n = 1 << i;
339
340             let fixture = bench_fixture::big_struct_n(n);
341             let (analysis, file_id) = fixture::file(&fixture);
342
343             let time = Instant::now();
344
345             let hash = analysis
346                 .highlight(file_id)
347                 .unwrap()
348                 .iter()
349                 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
350                 .count();
351             assert!(hash > n as usize);
352
353             let elapsed = time.elapsed();
354             al.sample(n as f64, elapsed.as_millis() as f64);
355         }
356     }
357 }
358
359 #[test]
360 fn benchmark_syntax_highlighting_parser() {
361     if skip_slow_tests() {
362         return;
363     }
364
365     let fixture = bench_fixture::glorious_old_parser();
366     let (analysis, file_id) = fixture::file(&fixture);
367
368     let hash = {
369         let _pt = bench("syntax highlighting parser");
370         analysis
371             .highlight(file_id)
372             .unwrap()
373             .iter()
374             .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
375             .count()
376     };
377     assert_eq!(hash, 1616);
378 }
379
380 #[test]
381 fn test_ranges() {
382     let (analysis, file_id) = fixture::file(
383         r#"
384 #[derive(Clone, Debug)]
385 struct Foo {
386     pub x: i32,
387     pub y: i32,
388 }
389 "#,
390     );
391
392     // The "x"
393     let highlights = &analysis
394         .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) })
395         .unwrap();
396
397     assert_eq!(&highlights[0].highlight.to_string(), "field.declaration.public");
398 }
399
400 #[test]
401 fn test_flattening() {
402     check_highlighting(
403         r##"
404 fn fixture(ra_fixture: &str) {}
405
406 fn main() {
407     fixture(r#"
408         trait Foo {
409             fn foo() {
410                 println!("2 + 2 = {}", 4);
411             }
412         }"#
413     );
414 }"##
415         .trim(),
416         expect_file!["./test_data/highlight_injection.html"],
417         false,
418     );
419 }
420
421 #[test]
422 fn ranges_sorted() {
423     let (analysis, file_id) = fixture::file(
424         r#"
425 #[foo(bar = "bar")]
426 macro_rules! test {}
427 }"#
428         .trim(),
429     );
430     let _ = analysis.highlight(file_id).unwrap();
431 }
432
433 #[test]
434 fn test_string_highlighting() {
435     // The format string detection is based on macro-expansion,
436     // thus, we have to copy the macro definition from `std`
437     check_highlighting(
438         r#"
439 macro_rules! println {
440     ($($arg:tt)*) => ({
441         $crate::io::_print($crate::format_args_nl!($($arg)*));
442     })
443 }
444 #[rustc_builtin_macro]
445 #[macro_export]
446 macro_rules! format_args {}
447 #[rustc_builtin_macro]
448 #[macro_export]
449 macro_rules! const_format_args {}
450 #[rustc_builtin_macro]
451 #[macro_export]
452 macro_rules! format_args_nl {}
453
454 mod panic {
455     pub macro panic_2015 {
456         () => (
457             $crate::panicking::panic("explicit panic")
458         ),
459         ($msg:literal $(,)?) => (
460             $crate::panicking::panic($msg)
461         ),
462         // Use `panic_str` instead of `panic_display::<&str>` for non_fmt_panic lint.
463         ($msg:expr $(,)?) => (
464             $crate::panicking::panic_str($msg)
465         ),
466         // Special-case the single-argument case for const_panic.
467         ("{}", $arg:expr $(,)?) => (
468             $crate::panicking::panic_display(&$arg)
469         ),
470         ($fmt:expr, $($arg:tt)+) => (
471             $crate::panicking::panic_fmt($crate::const_format_args!($fmt, $($arg)+))
472         ),
473     }
474 }
475
476 #[rustc_builtin_macro(std_panic)]
477 #[macro_export]
478 macro_rules! panic {}
479 #[rustc_builtin_macro]
480 macro_rules! assert {}
481 #[rustc_builtin_macro]
482 macro_rules! asm {}
483
484 macro_rules! toho {
485     () => ($crate::panic!("not yet implemented"));
486     ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+)));
487 }
488
489 fn main() {
490     // from https://doc.rust-lang.org/std/fmt/index.html
491     println!("Hello");                 // => "Hello"
492     println!("Hello, {}!", "world");   // => "Hello, world!"
493     println!("The number is {}", 1);   // => "The number is 1"
494     println!("{:?}", (3, 4));          // => "(3, 4)"
495     println!("{value}", value=4);      // => "4"
496     println!("{} {}", 1, 2);           // => "1 2"
497     println!("{:04}", 42);             // => "0042" with leading zerosV
498     println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
499     println!("{argument}", argument = "test");   // => "test"
500     println!("{name} {}", 1, name = 2);          // => "2 1"
501     println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
502     println!("{{{}}}", 2);                       // => "{2}"
503     println!("Hello {:5}!", "x");
504     println!("Hello {:1$}!", "x", 5);
505     println!("Hello {1:0$}!", 5, "x");
506     println!("Hello {:width$}!", "x", width = 5);
507     println!("Hello {:<5}!", "x");
508     println!("Hello {:-<5}!", "x");
509     println!("Hello {:^5}!", "x");
510     println!("Hello {:>5}!", "x");
511     println!("Hello {:+}!", 5);
512     println!("{:#x}!", 27);
513     println!("Hello {:05}!", 5);
514     println!("Hello {:05}!", -5);
515     println!("{:#010x}!", 27);
516     println!("Hello {0} is {1:.5}", "x", 0.01);
517     println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
518     println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
519     println!("Hello {} is {:.*}",    "x", 5, 0.01);
520     println!("Hello {} is {2:.*}",   "x", 5, 0.01);
521     println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
522     println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
523     println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
524     println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
525     println!("Hello {{}}");
526     println!("{{ Hello");
527
528     println!(r"Hello, {}!", "world");
529
530     // escape sequences
531     println!("Hello\nWorld");
532     println!("\u{48}\x65\x6C\x6C\x6F World");
533
534     println!("{\x41}", A = 92);
535     println!("{ничоси}", ничоси = 92);
536
537     println!("{:x?} {} ", thingy, n2);
538     panic!("{}", 0);
539     panic!("more {}", 1);
540     assert!(true, "{}", 1);
541     assert!(true, "{} asdasd", 1);
542     toho!("{}fmt", 0);
543     asm!("mov eax, {0}");
544     format_args!(concat!("{}"), "{}");
545 }"#
546         .trim(),
547         expect_file!["./test_data/highlight_strings.html"],
548         false,
549     );
550 }
551
552 #[test]
553 fn test_unsafe_highlighting() {
554     check_highlighting(
555         r#"
556 static mut MUT_GLOBAL: Struct = Struct { field: 0 };
557 static GLOBAL: Struct = Struct { field: 0 };
558 unsafe fn unsafe_fn() {}
559
560 union Union {
561     a: u32,
562     b: f32,
563 }
564
565 struct Struct { field: i32 }
566 impl Struct {
567     unsafe fn unsafe_method(&self) {}
568 }
569
570 #[repr(packed)]
571 struct Packed {
572     a: u16,
573 }
574
575 unsafe trait UnsafeTrait {}
576 unsafe impl UnsafeTrait for Packed {}
577 impl !UnsafeTrait for () {}
578
579 fn unsafe_trait_bound<T: UnsafeTrait>(_: T) {}
580
581 trait DoTheAutoref {
582     fn calls_autoref(&self);
583 }
584
585 impl DoTheAutoref for u16 {
586     fn calls_autoref(&self) {}
587 }
588
589 fn main() {
590     let x = &5 as *const _ as *const usize;
591     let u = Union { b: 0 };
592     unsafe {
593         // unsafe fn and method calls
594         unsafe_fn();
595         let b = u.b;
596         match u {
597             Union { b: 0 } => (),
598             Union { a } => (),
599         }
600         Struct { field: 0 }.unsafe_method();
601
602         // unsafe deref
603         *x;
604
605         // unsafe access to a static mut
606         MUT_GLOBAL.field;
607         GLOBAL.field;
608
609         // unsafe ref of packed fields
610         let packed = Packed { a: 0 };
611         let a = &packed.a;
612         let ref a = packed.a;
613         let Packed { ref a } = packed;
614         let Packed { a: ref _a } = packed;
615
616         // unsafe auto ref of packed field
617         packed.a.calls_autoref();
618     }
619 }
620 "#
621         .trim(),
622         expect_file!["./test_data/highlight_unsafe.html"],
623         false,
624     );
625 }
626
627 #[test]
628 fn test_highlight_doc_comment() {
629     check_highlighting(
630         r#"
631 //! This is a module to test doc injection.
632 //! ```
633 //! fn test() {}
634 //! ```
635
636 /// ```
637 /// let _ = "early doctests should not go boom";
638 /// ```
639 struct Foo {
640     bar: bool,
641 }
642
643 /// This is an impl with a code block.
644 ///
645 /// ```
646 /// fn foo() {
647 ///
648 /// }
649 /// ```
650 impl Foo {
651     /// ```
652     /// let _ = "Call me
653     //    KILLER WHALE
654     ///     Ishmael.";
655     /// ```
656     pub const bar: bool = true;
657
658     /// Constructs a new `Foo`.
659     ///
660     /// # Examples
661     ///
662     /// ```
663     /// # #![allow(unused_mut)]
664     /// let mut foo: Foo = Foo::new();
665     /// ```
666     pub const fn new() -> Foo {
667         Foo { bar: true }
668     }
669
670     /// `bar` method on `Foo`.
671     ///
672     /// # Examples
673     ///
674     /// ```
675     /// use x::y;
676     ///
677     /// let foo = Foo::new();
678     ///
679     /// // calls bar on foo
680     /// assert!(foo.bar());
681     ///
682     /// let bar = foo.bar || Foo::bar;
683     ///
684     /// /* multi-line
685     ///        comment */
686     ///
687     /// let multi_line_string = "Foo
688     ///   bar\n
689     ///          ";
690     ///
691     /// ```
692     ///
693     /// ```rust,no_run
694     /// let foobar = Foo::new().bar();
695     /// ```
696     ///
697     /// ```sh
698     /// echo 1
699     /// ```
700     pub fn foo(&self) -> bool {
701         true
702     }
703 }
704
705 /// [`Foo`](Foo) is a struct
706 /// This function is > [`all_the_links`](all_the_links) <
707 /// [`noop`](noop) is a macro below
708 /// [`Item`] is a struct in the module [`module`]
709 ///
710 /// [`Item`]: module::Item
711 /// [mix_and_match]: ThisShouldntResolve
712 pub fn all_the_links() {}
713
714 pub mod module {
715     pub struct Item;
716 }
717
718 /// ```
719 /// macro_rules! noop { ($expr:expr) => { $expr }}
720 /// noop!(1);
721 /// ```
722 macro_rules! noop {
723     ($expr:expr) => {
724         $expr
725     }
726 }
727
728 /// ```rust
729 /// let _ = example(&[1, 2, 3]);
730 /// ```
731 ///
732 /// ```
733 /// loop {}
734 #[cfg_attr(not(feature = "false"), doc = "loop {}")]
735 #[doc = "loop {}"]
736 /// ```
737 ///
738 #[cfg_attr(feature = "alloc", doc = "```rust")]
739 #[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
740 /// let _ = example(&alloc::vec![1, 2, 3]);
741 /// ```
742 pub fn mix_and_match() {}
743
744 /**
745 It is beyond me why you'd use these when you got ///
746 ```rust
747 let _ = example(&[1, 2, 3]);
748 ```
749 [`block_comments2`] tests these with indentation
750  */
751 pub fn block_comments() {}
752
753 /**
754     Really, I don't get it
755     ```rust
756     let _ = example(&[1, 2, 3]);
757     ```
758     [`block_comments`] tests these without indentation
759 */
760 pub fn block_comments2() {}
761 "#
762         .trim(),
763         expect_file!["./test_data/highlight_doctest.html"],
764         false,
765     );
766 }
767
768 #[test]
769 fn test_extern_crate() {
770     check_highlighting(
771         r#"
772         //- /main.rs crate:main deps:std,alloc
773         extern crate std;
774         extern crate alloc as abc;
775         //- /std/lib.rs crate:std
776         pub struct S;
777         //- /alloc/lib.rs crate:alloc
778         pub struct A
779         "#,
780         expect_file!["./test_data/highlight_extern_crate.html"],
781         false,
782     );
783 }
784
785 #[test]
786 fn test_crate_root() {
787     check_highlighting(
788         r#"
789         //- minicore: iterators
790         //- /main.rs crate:main deps:foo
791         extern crate foo;
792         use core::iter;
793
794         pub const NINETY_TWO: u8 = 92;
795
796         use foo as foooo;
797
798         pub(crate) fn main() {
799             let baz = iter::repeat(92);
800         }
801
802         mod bar {
803             pub(in super) const FORTY_TWO: u8 = 42;
804
805             mod baz {
806                 use super::super::NINETY_TWO;
807                 use crate::foooo::Point;
808
809                 pub(in super::super) const TWENTY_NINE: u8 = 29;
810             }
811         }
812         //- /foo.rs crate:foo
813         struct Point {
814             x: u8,
815             y: u8,
816         }
817
818         mod inner {
819             pub(super) fn swap(p: crate::Point) -> crate::Point {
820                 crate::Point { x: p.y, y: p.x }
821             }
822         }
823         "#,
824         expect_file!["./test_data/highlight_crate_root.html"],
825         false,
826     );
827 }
828
829 #[test]
830 fn test_default_library() {
831     check_highlighting(
832         r#"
833         //- minicore: option, iterators
834         use core::iter;
835
836         fn main() {
837             let foo = Some(92);
838             let nums = iter::repeat(foo.unwrap());
839         }
840         "#,
841         expect_file!["./test_data/highlight_default_library.html"],
842         false,
843     );
844 }
845
846 #[test]
847 fn test_associated_function() {
848     check_highlighting(
849         r#"
850 fn not_static() {}
851
852 struct foo {}
853
854 impl foo {
855     pub fn is_static() {}
856     pub fn is_not_static(&self) {}
857 }
858
859 trait t {
860     fn t_is_static() {}
861     fn t_is_not_static(&self) {}
862 }
863
864 impl t for foo {
865     pub fn is_static() {}
866     pub fn is_not_static(&self) {}
867 }
868         "#,
869         expect_file!["./test_data/highlight_assoc_functions.html"],
870         false,
871     )
872 }
873
874 #[test]
875 fn test_injection() {
876     check_highlighting(
877         r##"
878 fn f(ra_fixture: &str) {}
879 fn main() {
880     f(r"
881 fn foo() {
882     foo(\$0{
883         92
884     }\$0)
885 }");
886 }
887     "##,
888         expect_file!["./test_data/injection.html"],
889         false,
890     );
891 }
892
893 /// Highlights the code given by the `ra_fixture` argument, renders the
894 /// result as HTML, and compares it with the HTML file given as `snapshot`.
895 /// Note that the `snapshot` file is overwritten by the rendered HTML.
896 fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
897     let (analysis, file_id) = fixture::file(ra_fixture);
898     let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
899     expect.assert_eq(actual_html)
900 }