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