]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/syntax_highlighting/tests.rs
Merge #8831
[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 use inner::{self as inner_mod};
14 mod inner {}
15
16 #[rustc_builtin_macro]
17 macro Copy {}
18
19 // Needed for function consuming vs normal
20 pub mod marker {
21     #[lang = "copy"]
22     pub trait Copy {}
23 }
24
25 pub mod ops {
26     #[lang = "fn_once"]
27     pub trait FnOnce<Args> {}
28
29     #[lang = "fn_mut"]
30     pub trait FnMut<Args>: FnOnce<Args> {}
31
32     #[lang = "fn"]
33     pub trait Fn<Args>: FnMut<Args> {}
34 }
35
36
37 struct Foo {
38     pub x: i32,
39     pub y: i32,
40 }
41
42 trait Bar {
43     fn bar(&self) -> i32;
44 }
45
46 impl Bar for Foo {
47     fn bar(&self) -> i32 {
48         self.x
49     }
50 }
51
52 impl Foo {
53     fn baz(mut self, f: Foo) -> i32 {
54         f.baz(self)
55     }
56
57     fn qux(&mut self) {
58         self.x = 0;
59     }
60
61     fn quop(&self) -> i32 {
62         self.x
63     }
64 }
65
66 #[derive(Copy)]
67 struct FooCopy {
68     x: u32,
69 }
70
71 impl FooCopy {
72     fn baz(self, f: FooCopy) -> u32 {
73         f.baz(self)
74     }
75
76     fn qux(&mut self) {
77         self.x = 0;
78     }
79
80     fn quop(&self) -> u32 {
81         self.x
82     }
83 }
84
85 fn str() {
86     str();
87 }
88
89 static mut STATIC_MUT: i32 = 0;
90
91 fn foo<'a, T>() -> T {
92     foo::<'a, i32>()
93 }
94
95 fn never() -> ! {
96     loop {}
97 }
98
99 fn const_param<const FOO: usize>() -> usize {
100     FOO
101 }
102
103 use ops::Fn;
104 fn baz<F: Fn() -> ()>(f: F) {
105     f()
106 }
107
108 fn foobar() -> impl Copy {}
109
110 fn foo() {
111     let bar = foobar();
112 }
113
114 macro_rules! def_fn {
115     ($($tt:tt)*) => {$($tt)*}
116 }
117
118 def_fn! {
119     fn bar() -> u32 {
120         100
121     }
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
148     let mut vec = Vec::new();
149     if true {
150         let x = 92;
151         vec.push(Foo { x, y: 1 });
152     }
153     unsafe {
154         vec.set_len(0);
155         STATIC_MUT = 1;
156     }
157
158     for e in vec {
159         // Do nothing
160     }
161
162     noop!(noop!(1));
163
164     let mut x = 42;
165     let y = &mut x;
166     let z = &y;
167
168     let Foo { x: z, y } = Foo { x: z, y };
169
170     y;
171
172     let mut foo = Foo { x, y: x };
173     let foo2 = Foo { x, y: x };
174     foo.quop();
175     foo.qux();
176     foo.baz(foo2);
177
178     let mut copy = FooCopy { x };
179     copy.quop();
180     copy.qux();
181     copy.baz(copy);
182
183     let a = |x| x;
184     let bar = Foo::baz;
185
186     let baz = -42;
187     let baz = -baz;
188
189     let _ = !true;
190
191     'foo: loop {
192         break 'foo;
193         continue 'foo;
194     }
195 }
196
197 enum Option<T> {
198     Some(T),
199     None,
200 }
201 use Option::*;
202
203 impl<T> Option<T> {
204     fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
205         match other {
206             None => unimplemented!(),
207             Nope => Nope,
208         }
209     }
210 }
211
212 async fn learn_and_sing() {
213     let song = learn_song().await;
214     sing_song(song).await;
215 }
216
217 async fn async_main() {
218     let f1 = learn_and_sing();
219     let f2 = dance();
220     futures::join!(f1, f2);
221 }
222 "#
223         .trim(),
224         expect_file!["./test_data/highlighting.html"],
225         false,
226     );
227 }
228
229 #[test]
230 fn test_rainbow_highlighting() {
231     check_highlighting(
232         r#"
233 fn main() {
234     let hello = "hello";
235     let x = hello.to_string();
236     let y = hello.to_string();
237
238     let x = "other color please!";
239     let y = x.to_string();
240 }
241
242 fn bar() {
243     let mut hello = "hello";
244 }
245 "#
246         .trim(),
247         expect_file!["./test_data/rainbow_highlighting.html"],
248         true,
249     );
250 }
251
252 #[test]
253 fn benchmark_syntax_highlighting_long_struct() {
254     if skip_slow_tests() {
255         return;
256     }
257
258     let fixture = bench_fixture::big_struct();
259     let (analysis, file_id) = fixture::file(&fixture);
260
261     let hash = {
262         let _pt = bench("syntax highlighting long struct");
263         analysis
264             .highlight(file_id)
265             .unwrap()
266             .iter()
267             .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
268             .count()
269     };
270     assert_eq!(hash, 2001);
271 }
272
273 #[test]
274 fn syntax_highlighting_not_quadratic() {
275     if skip_slow_tests() {
276         return;
277     }
278
279     let mut al = AssertLinear::default();
280     while al.next_round() {
281         for i in 6..=10 {
282             let n = 1 << i;
283
284             let fixture = bench_fixture::big_struct_n(n);
285             let (analysis, file_id) = fixture::file(&fixture);
286
287             let time = Instant::now();
288
289             let hash = analysis
290                 .highlight(file_id)
291                 .unwrap()
292                 .iter()
293                 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
294                 .count();
295             assert!(hash > n as usize);
296
297             let elapsed = time.elapsed();
298             al.sample(n as f64, elapsed.as_millis() as f64);
299         }
300     }
301 }
302
303 #[test]
304 fn benchmark_syntax_highlighting_parser() {
305     if skip_slow_tests() {
306         return;
307     }
308
309     let fixture = bench_fixture::glorious_old_parser();
310     let (analysis, file_id) = fixture::file(&fixture);
311
312     let hash = {
313         let _pt = bench("syntax highlighting parser");
314         analysis
315             .highlight(file_id)
316             .unwrap()
317             .iter()
318             .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
319             .count()
320     };
321     assert_eq!(hash, 1632);
322 }
323
324 #[test]
325 fn test_ranges() {
326     let (analysis, file_id) = fixture::file(
327         r#"
328 #[derive(Clone, Debug)]
329 struct Foo {
330     pub x: i32,
331     pub y: i32,
332 }
333 "#,
334     );
335
336     // The "x"
337     let highlights = &analysis
338         .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) })
339         .unwrap();
340
341     assert_eq!(&highlights[0].highlight.to_string(), "field.declaration");
342 }
343
344 #[test]
345 fn test_flattening() {
346     check_highlighting(
347         r##"
348 fn fixture(ra_fixture: &str) {}
349
350 fn main() {
351     fixture(r#"
352         trait Foo {
353             fn foo() {
354                 println!("2 + 2 = {}", 4);
355             }
356         }"#
357     );
358 }"##
359         .trim(),
360         expect_file!["./test_data/highlight_injection.html"],
361         false,
362     );
363 }
364
365 #[test]
366 fn ranges_sorted() {
367     let (analysis, file_id) = fixture::file(
368         r#"
369 #[foo(bar = "bar")]
370 macro_rules! test {}
371 }"#
372         .trim(),
373     );
374     let _ = analysis.highlight(file_id).unwrap();
375 }
376
377 #[test]
378 fn test_string_highlighting() {
379     // The format string detection is based on macro-expansion,
380     // thus, we have to copy the macro definition from `std`
381     check_highlighting(
382         r#"
383 macro_rules! println {
384     ($($arg:tt)*) => ({
385         $crate::io::_print($crate::format_args_nl!($($arg)*));
386     })
387 }
388 #[rustc_builtin_macro]
389 macro_rules! format_args_nl {
390     ($fmt:expr) => {{ /* compiler built-in */ }};
391     ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
392 }
393
394 fn main() {
395     // from https://doc.rust-lang.org/std/fmt/index.html
396     println!("Hello");                 // => "Hello"
397     println!("Hello, {}!", "world");   // => "Hello, world!"
398     println!("The number is {}", 1);   // => "The number is 1"
399     println!("{:?}", (3, 4));          // => "(3, 4)"
400     println!("{value}", value=4);      // => "4"
401     println!("{} {}", 1, 2);           // => "1 2"
402     println!("{:04}", 42);             // => "0042" with leading zerosV
403     println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
404     println!("{argument}", argument = "test");   // => "test"
405     println!("{name} {}", 1, name = 2);          // => "2 1"
406     println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
407     println!("{{{}}}", 2);                       // => "{2}"
408     println!("Hello {:5}!", "x");
409     println!("Hello {:1$}!", "x", 5);
410     println!("Hello {1:0$}!", 5, "x");
411     println!("Hello {:width$}!", "x", width = 5);
412     println!("Hello {:<5}!", "x");
413     println!("Hello {:-<5}!", "x");
414     println!("Hello {:^5}!", "x");
415     println!("Hello {:>5}!", "x");
416     println!("Hello {:+}!", 5);
417     println!("{:#x}!", 27);
418     println!("Hello {:05}!", 5);
419     println!("Hello {:05}!", -5);
420     println!("{:#010x}!", 27);
421     println!("Hello {0} is {1:.5}", "x", 0.01);
422     println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
423     println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
424     println!("Hello {} is {:.*}",    "x", 5, 0.01);
425     println!("Hello {} is {2:.*}",   "x", 5, 0.01);
426     println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
427     println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
428     println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
429     println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
430     println!("Hello {{}}");
431     println!("{{ Hello");
432
433     println!(r"Hello, {}!", "world");
434
435     // escape sequences
436     println!("Hello\nWorld");
437     println!("\u{48}\x65\x6C\x6C\x6F World");
438
439     println!("{\x41}", A = 92);
440     println!("{ничоси}", ничоси = 92);
441
442     println!("{:x?} {} ", thingy, n2);
443 }"#
444         .trim(),
445         expect_file!["./test_data/highlight_strings.html"],
446         false,
447     );
448 }
449
450 #[test]
451 fn test_unsafe_highlighting() {
452     check_highlighting(
453         r#"
454 unsafe fn unsafe_fn() {}
455
456 union Union {
457     a: u32,
458     b: f32,
459 }
460
461 struct HasUnsafeFn;
462
463 impl HasUnsafeFn {
464     unsafe fn unsafe_method(&self) {}
465 }
466
467 struct TypeForStaticMut {
468     a: u8
469 }
470
471 static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
472
473 #[repr(packed)]
474 struct Packed {
475     a: u16,
476 }
477
478 trait DoTheAutoref {
479     fn calls_autoref(&self);
480 }
481
482 impl DoTheAutoref for u16 {
483     fn calls_autoref(&self) {}
484 }
485
486 fn main() {
487     let x = &5 as *const _ as *const usize;
488     let u = Union { b: 0 };
489     unsafe {
490         // unsafe fn and method calls
491         unsafe_fn();
492         let b = u.b;
493         match u {
494             Union { b: 0 } => (),
495             Union { a } => (),
496         }
497         HasUnsafeFn.unsafe_method();
498
499         // unsafe deref
500         let y = *x;
501
502         // unsafe access to a static mut
503         let a = global_mut.a;
504
505         // unsafe ref of packed fields
506         let packed = Packed { a: 0 };
507         let a = &packed.a;
508         let ref a = packed.a;
509         let Packed { ref a } = packed;
510         let Packed { a: ref _a } = packed;
511
512         // unsafe auto ref of packed field
513         packed.a.calls_autoref();
514     }
515 }
516 "#
517         .trim(),
518         expect_file!["./test_data/highlight_unsafe.html"],
519         false,
520     );
521 }
522
523 #[test]
524 fn test_highlight_doc_comment() {
525     check_highlighting(
526         r#"
527 /// ```
528 /// let _ = "early doctests should not go boom";
529 /// ```
530 struct Foo {
531     bar: bool,
532 }
533
534 impl Foo {
535     /// ```
536     /// let _ = "Call me
537     //    KILLER WHALE
538     ///     Ishmael.";
539     /// ```
540     pub const bar: bool = true;
541
542     /// Constructs a new `Foo`.
543     ///
544     /// # Examples
545     ///
546     /// ```
547     /// # #![allow(unused_mut)]
548     /// let mut foo: Foo = Foo::new();
549     /// ```
550     pub const fn new() -> Foo {
551         Foo { bar: true }
552     }
553
554     /// `bar` method on `Foo`.
555     ///
556     /// # Examples
557     ///
558     /// ```
559     /// use x::y;
560     ///
561     /// let foo = Foo::new();
562     ///
563     /// // calls bar on foo
564     /// assert!(foo.bar());
565     ///
566     /// let bar = foo.bar || Foo::bar;
567     ///
568     /// /* multi-line
569     ///        comment */
570     ///
571     /// let multi_line_string = "Foo
572     ///   bar\n
573     ///          ";
574     ///
575     /// ```
576     ///
577     /// ```rust,no_run
578     /// let foobar = Foo::new().bar();
579     /// ```
580     ///
581     /// ```sh
582     /// echo 1
583     /// ```
584     pub fn foo(&self) -> bool {
585         true
586     }
587 }
588
589 /// [`Foo`](Foo) is a struct
590 /// This function is > [`all_the_links`](all_the_links) <
591 /// [`noop`](noop) is a macro below
592 /// [`Item`] is a struct in the module [`module`]
593 ///
594 /// [`Item`]: module::Item
595 /// [mix_and_match]: ThisShouldntResolve
596 pub fn all_the_links() {}
597
598 pub mod module {
599     pub struct Item;
600 }
601
602 /// ```
603 /// noop!(1);
604 /// ```
605 macro_rules! noop {
606     ($expr:expr) => {
607         $expr
608     }
609 }
610
611 /// ```rust
612 /// let _ = example(&[1, 2, 3]);
613 /// ```
614 ///
615 /// ```
616 /// loop {}
617 #[cfg_attr(not(feature = "false"), doc = "loop {}")]
618 #[doc = "loop {}"]
619 /// ```
620 ///
621 #[cfg_attr(feature = "alloc", doc = "```rust")]
622 #[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
623 /// let _ = example(&alloc::vec![1, 2, 3]);
624 /// ```
625 pub fn mix_and_match() {}
626
627 /**
628 It is beyond me why you'd use these when you got ///
629 ```rust
630 let _ = example(&[1, 2, 3]);
631 ```
632 [`block_comments2`] tests these with indentation
633  */
634 pub fn block_comments() {}
635
636 /**
637     Really, I don't get it
638     ```rust
639     let _ = example(&[1, 2, 3]);
640     ```
641     [`block_comments`] tests these without indentation
642 */
643 pub fn block_comments2() {}
644 "#
645         .trim(),
646         expect_file!["./test_data/highlight_doctest.html"],
647         false,
648     );
649 }
650
651 #[test]
652 fn test_extern_crate() {
653     check_highlighting(
654         r#"
655         //- /main.rs crate:main deps:std,alloc
656         extern crate std;
657         extern crate alloc as abc;
658         //- /std/lib.rs crate:std
659         pub struct S;
660         //- /alloc/lib.rs crate:alloc
661         pub struct A
662         "#,
663         expect_file!["./test_data/highlight_extern_crate.html"],
664         false,
665     );
666 }
667
668 #[test]
669 fn test_associated_function() {
670     check_highlighting(
671         r#"
672 fn not_static() {}
673
674 struct foo {}
675
676 impl foo {
677     pub fn is_static() {}
678     pub fn is_not_static(&self) {}
679 }
680
681 trait t {
682     fn t_is_static() {}
683     fn t_is_not_static(&self) {}
684 }
685
686 impl t for foo {
687     pub fn is_static() {}
688     pub fn is_not_static(&self) {}
689 }
690         "#,
691         expect_file!["./test_data/highlight_assoc_functions.html"],
692         false,
693     )
694 }
695
696 #[test]
697 fn test_injection() {
698     check_highlighting(
699         r##"
700 fn f(ra_fixture: &str) {}
701 fn main() {
702     f(r"
703 fn foo() {
704     foo(\$0{
705         92
706     }\$0)
707 }");
708 }
709     "##,
710         expect_file!["./test_data/injection.html"],
711         false,
712     );
713 }
714
715 /// Highlights the code given by the `ra_fixture` argument, renders the
716 /// result as HTML, and compares it with the HTML file given as `snapshot`.
717 /// Note that the `snapshot` file is overwritten by the rendered HTML.
718 fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
719     let (analysis, file_id) = fixture::file(ra_fixture);
720     let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
721     expect.assert_eq(actual_html)
722 }