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