]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/syntax_highlighting/tests.rs
Fix block comment intra doc link injection ranges
[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         .trim(),
213         expect_file!["./test_data/highlighting.html"],
214         false,
215     );
216 }
217
218 #[test]
219 fn test_rainbow_highlighting() {
220     check_highlighting(
221         r#"
222 fn main() {
223     let hello = "hello";
224     let x = hello.to_string();
225     let y = hello.to_string();
226
227     let x = "other color please!";
228     let y = x.to_string();
229 }
230
231 fn bar() {
232     let mut hello = "hello";
233 }
234 "#
235         .trim(),
236         expect_file!["./test_data/rainbow_highlighting.html"],
237         true,
238     );
239 }
240
241 #[test]
242 fn benchmark_syntax_highlighting_long_struct() {
243     if skip_slow_tests() {
244         return;
245     }
246
247     let fixture = bench_fixture::big_struct();
248     let (analysis, file_id) = fixture::file(&fixture);
249
250     let hash = {
251         let _pt = bench("syntax highlighting long struct");
252         analysis
253             .highlight(file_id)
254             .unwrap()
255             .iter()
256             .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
257             .count()
258     };
259     assert_eq!(hash, 2001);
260 }
261
262 #[test]
263 fn syntax_highlighting_not_quadratic() {
264     if skip_slow_tests() {
265         return;
266     }
267
268     let mut al = AssertLinear::default();
269     while al.next_round() {
270         for i in 6..=10 {
271             let n = 1 << i;
272
273             let fixture = bench_fixture::big_struct_n(n);
274             let (analysis, file_id) = fixture::file(&fixture);
275
276             let time = Instant::now();
277
278             let hash = analysis
279                 .highlight(file_id)
280                 .unwrap()
281                 .iter()
282                 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
283                 .count();
284             assert!(hash > n as usize);
285
286             let elapsed = time.elapsed();
287             al.sample(n as f64, elapsed.as_millis() as f64);
288         }
289     }
290 }
291
292 #[test]
293 fn benchmark_syntax_highlighting_parser() {
294     if skip_slow_tests() {
295         return;
296     }
297
298     let fixture = bench_fixture::glorious_old_parser();
299     let (analysis, file_id) = fixture::file(&fixture);
300
301     let hash = {
302         let _pt = bench("syntax highlighting parser");
303         analysis
304             .highlight(file_id)
305             .unwrap()
306             .iter()
307             .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
308             .count()
309     };
310     assert_eq!(hash, 1632);
311 }
312
313 #[test]
314 fn test_ranges() {
315     let (analysis, file_id) = fixture::file(
316         r#"
317 #[derive(Clone, Debug)]
318 struct Foo {
319     pub x: i32,
320     pub y: i32,
321 }
322 "#,
323     );
324
325     // The "x"
326     let highlights = &analysis
327         .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) })
328         .unwrap();
329
330     assert_eq!(&highlights[0].highlight.to_string(), "field.declaration");
331 }
332
333 #[test]
334 fn test_flattening() {
335     check_highlighting(
336         r##"
337 fn fixture(ra_fixture: &str) {}
338
339 fn main() {
340     fixture(r#"
341         trait Foo {
342             fn foo() {
343                 println!("2 + 2 = {}", 4);
344             }
345         }"#
346     );
347 }"##
348         .trim(),
349         expect_file!["./test_data/highlight_injection.html"],
350         false,
351     );
352 }
353
354 #[test]
355 fn ranges_sorted() {
356     let (analysis, file_id) = fixture::file(
357         r#"
358 #[foo(bar = "bar")]
359 macro_rules! test {}
360 }"#
361         .trim(),
362     );
363     let _ = analysis.highlight(file_id).unwrap();
364 }
365
366 #[test]
367 fn test_string_highlighting() {
368     // The format string detection is based on macro-expansion,
369     // thus, we have to copy the macro definition from `std`
370     check_highlighting(
371         r#"
372 macro_rules! println {
373     ($($arg:tt)*) => ({
374         $crate::io::_print($crate::format_args_nl!($($arg)*));
375     })
376 }
377 #[rustc_builtin_macro]
378 macro_rules! format_args_nl {
379     ($fmt:expr) => {{ /* compiler built-in */ }};
380     ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
381 }
382
383 fn main() {
384     // from https://doc.rust-lang.org/std/fmt/index.html
385     println!("Hello");                 // => "Hello"
386     println!("Hello, {}!", "world");   // => "Hello, world!"
387     println!("The number is {}", 1);   // => "The number is 1"
388     println!("{:?}", (3, 4));          // => "(3, 4)"
389     println!("{value}", value=4);      // => "4"
390     println!("{} {}", 1, 2);           // => "1 2"
391     println!("{:04}", 42);             // => "0042" with leading zerosV
392     println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
393     println!("{argument}", argument = "test");   // => "test"
394     println!("{name} {}", 1, name = 2);          // => "2 1"
395     println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
396     println!("{{{}}}", 2);                       // => "{2}"
397     println!("Hello {:5}!", "x");
398     println!("Hello {:1$}!", "x", 5);
399     println!("Hello {1:0$}!", 5, "x");
400     println!("Hello {:width$}!", "x", width = 5);
401     println!("Hello {:<5}!", "x");
402     println!("Hello {:-<5}!", "x");
403     println!("Hello {:^5}!", "x");
404     println!("Hello {:>5}!", "x");
405     println!("Hello {:+}!", 5);
406     println!("{:#x}!", 27);
407     println!("Hello {:05}!", 5);
408     println!("Hello {:05}!", -5);
409     println!("{:#010x}!", 27);
410     println!("Hello {0} is {1:.5}", "x", 0.01);
411     println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
412     println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
413     println!("Hello {} is {:.*}",    "x", 5, 0.01);
414     println!("Hello {} is {2:.*}",   "x", 5, 0.01);
415     println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
416     println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
417     println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
418     println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
419     println!("Hello {{}}");
420     println!("{{ Hello");
421
422     println!(r"Hello, {}!", "world");
423
424     // escape sequences
425     println!("Hello\nWorld");
426     println!("\u{48}\x65\x6C\x6C\x6F World");
427
428     println!("{\x41}", A = 92);
429     println!("{ничоси}", ничоси = 92);
430
431     println!("{:x?} {} ", thingy, n2);
432 }"#
433         .trim(),
434         expect_file!["./test_data/highlight_strings.html"],
435         false,
436     );
437 }
438
439 #[test]
440 fn test_unsafe_highlighting() {
441     check_highlighting(
442         r#"
443 unsafe fn unsafe_fn() {}
444
445 union Union {
446     a: u32,
447     b: f32,
448 }
449
450 struct HasUnsafeFn;
451
452 impl HasUnsafeFn {
453     unsafe fn unsafe_method(&self) {}
454 }
455
456 struct TypeForStaticMut {
457     a: u8
458 }
459
460 static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
461
462 #[repr(packed)]
463 struct Packed {
464     a: u16,
465 }
466
467 trait DoTheAutoref {
468     fn calls_autoref(&self);
469 }
470
471 impl DoTheAutoref for u16 {
472     fn calls_autoref(&self) {}
473 }
474
475 fn main() {
476     let x = &5 as *const _ as *const usize;
477     let u = Union { b: 0 };
478     unsafe {
479         // unsafe fn and method calls
480         unsafe_fn();
481         let b = u.b;
482         match u {
483             Union { b: 0 } => (),
484             Union { a } => (),
485         }
486         HasUnsafeFn.unsafe_method();
487
488         // unsafe deref
489         let y = *x;
490
491         // unsafe access to a static mut
492         let a = global_mut.a;
493
494         // unsafe ref of packed fields
495         let packed = Packed { a: 0 };
496         let a = &packed.a;
497         let ref a = packed.a;
498         let Packed { ref a } = packed;
499         let Packed { a: ref _a } = packed;
500
501         // unsafe auto ref of packed field
502         packed.a.calls_autoref();
503     }
504 }
505 "#
506         .trim(),
507         expect_file!["./test_data/highlight_unsafe.html"],
508         false,
509     );
510 }
511
512 #[test]
513 fn test_highlight_doc_comment() {
514     check_highlighting(
515         r#"
516 /// ```
517 /// let _ = "early doctests should not go boom";
518 /// ```
519 struct Foo {
520     bar: bool,
521 }
522
523 impl Foo {
524     /// ```
525     /// let _ = "Call me
526     //    KILLER WHALE
527     ///     Ishmael.";
528     /// ```
529     pub const bar: bool = true;
530
531     /// Constructs a new `Foo`.
532     ///
533     /// # Examples
534     ///
535     /// ```
536     /// # #![allow(unused_mut)]
537     /// let mut foo: Foo = Foo::new();
538     /// ```
539     pub const fn new() -> Foo {
540         Foo { bar: true }
541     }
542
543     /// `bar` method on `Foo`.
544     ///
545     /// # Examples
546     ///
547     /// ```
548     /// use x::y;
549     ///
550     /// let foo = Foo::new();
551     ///
552     /// // calls bar on foo
553     /// assert!(foo.bar());
554     ///
555     /// let bar = foo.bar || Foo::bar;
556     ///
557     /// /* multi-line
558     ///        comment */
559     ///
560     /// let multi_line_string = "Foo
561     ///   bar\n
562     ///          ";
563     ///
564     /// ```
565     ///
566     /// ```rust,no_run
567     /// let foobar = Foo::new().bar();
568     /// ```
569     ///
570     /// ```sh
571     /// echo 1
572     /// ```
573     pub fn foo(&self) -> bool {
574         true
575     }
576 }
577
578 /// [`Foo`](Foo) is a struct
579 /// This function is > [`all_the_links`](all_the_links) <
580 /// [`noop`](noop) is a macro below
581 /// [`Item`] is a struct in the module [`module`]
582 ///
583 /// [`Item`]: module::Item
584 /// [mix_and_match]: ThisShouldntResolve
585 pub fn all_the_links() {}
586
587 pub mod module {
588     pub struct Item;
589 }
590
591 /// ```
592 /// noop!(1);
593 /// ```
594 macro_rules! noop {
595     ($expr:expr) => {
596         $expr
597     }
598 }
599
600 /// ```rust
601 /// let _ = example(&[1, 2, 3]);
602 /// ```
603 ///
604 /// ```
605 /// loop {}
606 #[cfg_attr(not(feature = "false"), doc = "loop {}")]
607 #[doc = "loop {}"]
608 /// ```
609 ///
610 #[cfg_attr(feature = "alloc", doc = "```rust")]
611 #[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
612 /// let _ = example(&alloc::vec![1, 2, 3]);
613 /// ```
614 pub fn mix_and_match() {}
615
616 /**
617 It is beyond me why you'd use these when you got ///
618 ```rust
619 let _ = example(&[1, 2, 3]);
620 ```
621 [`block_comments2`] tests these with indentation
622  */
623 pub fn block_comments() {}
624
625 /**
626     Really, I don't get it
627     ```rust
628     let _ = example(&[1, 2, 3]);
629     ```
630     [`block_comments`] tests these without indentation
631 */
632 pub fn block_comments2() {}
633 "#
634         .trim(),
635         expect_file!["./test_data/highlight_doctest.html"],
636         false,
637     );
638 }
639
640 #[test]
641 fn test_extern_crate() {
642     check_highlighting(
643         r#"
644         //- /main.rs crate:main deps:std,alloc
645         extern crate std;
646         extern crate alloc as abc;
647         //- /std/lib.rs crate:std
648         pub struct S;
649         //- /alloc/lib.rs crate:alloc
650         pub struct A
651         "#,
652         expect_file!["./test_data/highlight_extern_crate.html"],
653         false,
654     );
655 }
656
657 #[test]
658 fn test_associated_function() {
659     check_highlighting(
660         r#"
661 fn not_static() {}
662
663 struct foo {}
664
665 impl foo {
666     pub fn is_static() {}
667     pub fn is_not_static(&self) {}
668 }
669
670 trait t {
671     fn t_is_static() {}
672     fn t_is_not_static(&self) {}
673 }
674
675 impl t for foo {
676     pub fn is_static() {}
677     pub fn is_not_static(&self) {}
678 }
679         "#,
680         expect_file!["./test_data/highlight_assoc_functions.html"],
681         false,
682     )
683 }
684
685 #[test]
686 fn test_injection() {
687     check_highlighting(
688         r##"
689 fn f(ra_fixture: &str) {}
690 fn main() {
691     f(r"
692 fn foo() {
693     foo(\$0{
694         92
695     }\$0)
696 }");
697 }
698     "##,
699         expect_file!["./test_data/injection.html"],
700         false,
701     );
702 }
703
704 /// Highlights the code given by the `ra_fixture` argument, renders the
705 /// result as HTML, and compares it with the HTML file given as `snapshot`.
706 /// Note that the `snapshot` file is overwritten by the rendered HTML.
707 fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
708     let (analysis, file_id) = fixture::file(ra_fixture);
709     let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
710     expect.assert_eq(actual_html)
711 }