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