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