]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #10093
[rust.git] / crates / ide / src / highlight_related.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::FilePosition,
4     defs::{Definition, NameClass, NameRefClass},
5     helpers::{for_each_break_expr, for_each_tail_expr, pick_best_token},
6     search::{FileReference, ReferenceAccess, SearchScope},
7     RootDatabase,
8 };
9 use rustc_hash::FxHashSet;
10 use syntax::{
11     ast::{self, LoopBodyOwner},
12     match_ast, AstNode,
13     SyntaxKind::IDENT,
14     SyntaxNode, SyntaxToken, TextRange, TextSize, T,
15 };
16
17 use crate::{display::TryToNav, references, NavigationTarget};
18
19 #[derive(PartialEq, Eq, Hash)]
20 pub struct HighlightedRange {
21     pub range: TextRange,
22     pub access: Option<ReferenceAccess>,
23 }
24
25 #[derive(Default, Clone)]
26 pub struct HighlightRelatedConfig {
27     pub references: bool,
28     pub exit_points: bool,
29     pub break_points: bool,
30     pub yield_points: bool,
31 }
32
33 // Feature: Highlight Related
34 //
35 // Highlights constructs related to the thing under the cursor:
36 // - if on an identifier, highlights all references to that identifier in the current file
37 // - if on an `async` or `await token, highlights all yield points for that async context
38 // - if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
39 // - if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
40 //
41 // Note: `?` and `->` do not currently trigger this behavior in the VSCode editor.
42 pub(crate) fn highlight_related(
43     sema: &Semantics<RootDatabase>,
44     config: HighlightRelatedConfig,
45     position: FilePosition,
46 ) -> Option<Vec<HighlightedRange>> {
47     let _p = profile::span("highlight_related");
48     let syntax = sema.parse(position.file_id).syntax().clone();
49
50     let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
51         T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
52         T![->] => 3,
53         kind if kind.is_keyword() => 2,
54         IDENT => 1,
55         _ => 0,
56     })?;
57
58     match token.kind() {
59         T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
60             highlight_exit_points(sema, token)
61         }
62         T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token),
63         T![await] | T![async] if config.yield_points => highlight_yield_points(token),
64         T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
65             highlight_break_points(token)
66         }
67         T![break] | T![loop] | T![while] if config.break_points => highlight_break_points(token),
68         _ if config.references => highlight_references(sema, &syntax, position),
69         _ => None,
70     }
71 }
72
73 fn highlight_references(
74     sema: &Semantics<RootDatabase>,
75     syntax: &SyntaxNode,
76     FilePosition { offset, file_id }: FilePosition,
77 ) -> Option<Vec<HighlightedRange>> {
78     let defs = find_defs(sema, syntax, offset);
79     let usages = defs
80         .iter()
81         .filter_map(|&d| {
82             d.usages(sema)
83                 .set_scope(Some(SearchScope::single_file(file_id)))
84                 .include_self_refs()
85                 .all()
86                 .references
87                 .remove(&file_id)
88         })
89         .flatten()
90         .map(|FileReference { access, range, .. }| HighlightedRange { range, access });
91
92     let declarations = defs.iter().flat_map(|def| {
93         match def {
94             &Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
95                 Some(NavigationTarget::from_module_to_decl(sema.db, module))
96             }
97             def => def.try_to_nav(sema.db),
98         }
99         .filter(|decl| decl.file_id == file_id)
100         .and_then(|decl| {
101             let range = decl.focus_range?;
102             let access = references::decl_access(&def, syntax, range);
103             Some(HighlightedRange { range, access })
104         })
105     });
106
107     let res: FxHashSet<_> = declarations.chain(usages).collect();
108     if res.is_empty() {
109         None
110     } else {
111         Some(res.into_iter().collect())
112     }
113 }
114
115 fn highlight_exit_points(
116     sema: &Semantics<RootDatabase>,
117     token: SyntaxToken,
118 ) -> Option<Vec<HighlightedRange>> {
119     fn hl(
120         sema: &Semantics<RootDatabase>,
121         body: Option<ast::Expr>,
122     ) -> Option<Vec<HighlightedRange>> {
123         let mut highlights = Vec::new();
124         let body = body?;
125         body.walk(&mut |expr| match expr {
126             ast::Expr::ReturnExpr(expr) => {
127                 if let Some(token) = expr.return_token() {
128                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
129                 }
130             }
131             ast::Expr::TryExpr(try_) => {
132                 if let Some(token) = try_.question_mark_token() {
133                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
134                 }
135             }
136             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
137                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
138                     highlights
139                         .push(HighlightedRange { access: None, range: expr.syntax().text_range() });
140                 }
141             }
142             _ => (),
143         });
144         let tail = match body {
145             ast::Expr::BlockExpr(b) => b.tail_expr(),
146             e => Some(e),
147         };
148
149         if let Some(tail) = tail {
150             for_each_tail_expr(&tail, &mut |tail| {
151                 let range = match tail {
152                     ast::Expr::BreakExpr(b) => b
153                         .break_token()
154                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
155                     _ => tail.syntax().text_range(),
156                 };
157                 highlights.push(HighlightedRange { access: None, range })
158             });
159         }
160         Some(highlights)
161     }
162     for anc in token.ancestors() {
163         return match_ast! {
164             match anc {
165                 ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
166                 ast::ClosureExpr(closure) => hl(sema, closure.body()),
167                 ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
168                     hl(sema, effect.block_expr().map(ast::Expr::BlockExpr))
169                 } else {
170                     continue;
171                 },
172                 _ => continue,
173             }
174         };
175     }
176     None
177 }
178
179 fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
180     fn hl(
181         token: Option<SyntaxToken>,
182         label: Option<ast::Label>,
183         body: Option<ast::BlockExpr>,
184     ) -> Option<Vec<HighlightedRange>> {
185         let mut highlights = Vec::new();
186         let range = cover_range(
187             token.map(|tok| tok.text_range()),
188             label.as_ref().map(|it| it.syntax().text_range()),
189         );
190         highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
191         for_each_break_expr(label, body, &mut |break_| {
192             let range = cover_range(
193                 break_.break_token().map(|it| it.text_range()),
194                 break_.lifetime().map(|it| it.syntax().text_range()),
195             );
196             highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
197         });
198         Some(highlights)
199     }
200     let parent = token.parent()?;
201     let lbl = match_ast! {
202         match parent {
203             ast::BreakExpr(b) => b.lifetime(),
204             ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
205             ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
206             ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
207             ast::EffectExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
208             _ => return None,
209         }
210     };
211     let lbl = lbl.as_ref();
212     let label_matches = |def_lbl: Option<ast::Label>| match lbl {
213         Some(lbl) => {
214             Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
215         }
216         None => true,
217     };
218     for anc in token.ancestors().flat_map(ast::Expr::cast) {
219         return match anc {
220             ast::Expr::LoopExpr(l) if label_matches(l.label()) => {
221                 hl(l.loop_token(), l.label(), l.loop_body())
222             }
223             ast::Expr::ForExpr(f) if label_matches(f.label()) => {
224                 hl(f.for_token(), f.label(), f.loop_body())
225             }
226             ast::Expr::WhileExpr(w) if label_matches(w.label()) => {
227                 hl(w.while_token(), w.label(), w.loop_body())
228             }
229             ast::Expr::EffectExpr(e) if e.label().is_some() && label_matches(e.label()) => {
230                 hl(None, e.label(), e.block_expr())
231             }
232             _ => continue,
233         };
234     }
235     None
236 }
237
238 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
239     fn hl(
240         async_token: Option<SyntaxToken>,
241         body: Option<ast::Expr>,
242     ) -> Option<Vec<HighlightedRange>> {
243         let mut highlights =
244             vec![HighlightedRange { access: None, range: async_token?.text_range() }];
245         if let Some(body) = body {
246             body.walk(&mut |expr| {
247                 if let ast::Expr::AwaitExpr(expr) = expr {
248                     if let Some(token) = expr.await_token() {
249                         highlights
250                             .push(HighlightedRange { access: None, range: token.text_range() });
251                     }
252                 }
253             });
254         }
255         Some(highlights)
256     }
257     for anc in token.ancestors() {
258         return match_ast! {
259             match anc {
260                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
261                 ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr().map(ast::Expr::BlockExpr)),
262                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
263                 _ => continue,
264             }
265         };
266     }
267     None
268 }
269
270 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
271     match (r0, r1) {
272         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
273         (Some(range), None) => Some(range),
274         (None, Some(range)) => Some(range),
275         (None, None) => None,
276     }
277 }
278
279 fn find_defs(
280     sema: &Semantics<RootDatabase>,
281     syntax: &SyntaxNode,
282     offset: TextSize,
283 ) -> FxHashSet<Definition> {
284     sema.find_nodes_at_offset_with_descend(syntax, offset)
285         .flat_map(|name_like| {
286             Some(match name_like {
287                 ast::NameLike::NameRef(name_ref) => {
288                     match NameRefClass::classify(sema, &name_ref)? {
289                         NameRefClass::Definition(def) => vec![def],
290                         NameRefClass::FieldShorthand { local_ref, field_ref } => {
291                             vec![Definition::Local(local_ref), Definition::Field(field_ref)]
292                         }
293                     }
294                 }
295                 ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
296                     NameClass::Definition(it) | NameClass::ConstReference(it) => vec![it],
297                     NameClass::PatFieldShorthand { local_def, field_ref } => {
298                         vec![Definition::Local(local_def), Definition::Field(field_ref)]
299                     }
300                 },
301                 ast::NameLike::Lifetime(lifetime) => {
302                     NameRefClass::classify_lifetime(sema, &lifetime)
303                         .and_then(|class| match class {
304                             NameRefClass::Definition(it) => Some(it),
305                             _ => None,
306                         })
307                         .or_else(|| {
308                             NameClass::classify_lifetime(sema, &lifetime)
309                                 .and_then(NameClass::defined)
310                         })
311                         .map(|it| vec![it])?
312                 }
313             })
314         })
315         .flatten()
316         .collect()
317 }
318
319 #[cfg(test)]
320 mod tests {
321     use crate::fixture;
322
323     use super::*;
324
325     fn check(ra_fixture: &str) {
326         let config = HighlightRelatedConfig {
327             break_points: true,
328             exit_points: true,
329             references: true,
330             yield_points: true,
331         };
332
333         check_with_config(ra_fixture, config);
334     }
335
336     fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
337         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
338
339         let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or(Vec::default());
340
341         let mut expected = annotations
342             .into_iter()
343             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
344             .collect::<Vec<_>>();
345
346         let mut actual = hls
347             .into_iter()
348             .map(|hl| {
349                 (
350                     hl.range,
351                     hl.access.map(|it| {
352                         match it {
353                             ReferenceAccess::Read => "read",
354                             ReferenceAccess::Write => "write",
355                         }
356                         .to_string()
357                     }),
358                 )
359             })
360             .collect::<Vec<_>>();
361         actual.sort_by_key(|(range, _)| range.start());
362         expected.sort_by_key(|(range, _)| range.start());
363
364         assert_eq!(expected, actual);
365     }
366
367     #[test]
368     fn test_hl_module() {
369         check(
370             r#"
371 //- /lib.rs
372 mod foo$0;
373  // ^^^
374 //- /foo.rs
375 struct Foo;
376 "#,
377         );
378     }
379
380     #[test]
381     fn test_hl_self_in_crate_root() {
382         check(
383             r#"
384 use self$0;
385 "#,
386         );
387     }
388
389     #[test]
390     fn test_hl_self_in_module() {
391         check(
392             r#"
393 //- /lib.rs
394 mod foo;
395 //- /foo.rs
396 use self$0;
397  // ^^^^
398 "#,
399         );
400     }
401
402     #[test]
403     fn test_hl_local() {
404         check(
405             r#"
406 fn foo() {
407     let mut bar = 3;
408          // ^^^ write
409     bar$0;
410  // ^^^ read
411 }
412 "#,
413         );
414     }
415
416     #[test]
417     fn test_multi_macro_usage() {
418         check(
419             r#"
420 macro_rules! foo {
421     ($ident:ident) => {
422         fn $ident() -> $ident { loop {} }
423         struct $ident;
424     }
425 }
426
427 foo!(bar$0);
428   // ^^^
429 fn foo() {
430     let bar: bar = bar();
431           // ^^^
432                 // ^^^
433 }
434 "#,
435         );
436         check(
437             r#"
438 macro_rules! foo {
439     ($ident:ident) => {
440         fn $ident() -> $ident { loop {} }
441         struct $ident;
442     }
443 }
444
445 foo!(bar);
446   // ^^^
447 fn foo() {
448     let bar: bar$0 = bar();
449           // ^^^
450 }
451 "#,
452         );
453     }
454
455     #[test]
456     fn test_hl_yield_points() {
457         check(
458             r#"
459 pub async fn foo() {
460  // ^^^^^
461     let x = foo()
462         .await$0
463       // ^^^^^
464         .await;
465       // ^^^^^
466     || { 0.await };
467     (async { 0.await }).await
468                      // ^^^^^
469 }
470 "#,
471         );
472     }
473
474     #[test]
475     fn test_hl_yield_points2() {
476         check(
477             r#"
478 pub async$0 fn foo() {
479  // ^^^^^
480     let x = foo()
481         .await
482       // ^^^^^
483         .await;
484       // ^^^^^
485     || { 0.await };
486     (async { 0.await }).await
487                      // ^^^^^
488 }
489 "#,
490         );
491     }
492
493     #[test]
494     fn test_hl_yield_nested_fn() {
495         check(
496             r#"
497 async fn foo() {
498     async fn foo2() {
499  // ^^^^^
500         async fn foo3() {
501             0.await
502         }
503         0.await$0
504        // ^^^^^
505     }
506     0.await
507 }
508 "#,
509         );
510     }
511
512     #[test]
513     fn test_hl_yield_nested_async_blocks() {
514         check(
515             r#"
516 async fn foo() {
517     (async {
518   // ^^^^^
519         (async {
520            0.await
521         }).await$0 }
522         // ^^^^^
523     ).await;
524 }
525 "#,
526         );
527     }
528
529     #[test]
530     fn test_hl_exit_points() {
531         check(
532             r#"
533 fn foo() -> u32 {
534     if true {
535         return$0 0;
536      // ^^^^^^
537     }
538
539     0?;
540   // ^
541     0xDEAD_BEEF
542  // ^^^^^^^^^^^
543 }
544 "#,
545         );
546     }
547
548     #[test]
549     fn test_hl_exit_points2() {
550         check(
551             r#"
552 fn foo() ->$0 u32 {
553     if true {
554         return 0;
555      // ^^^^^^
556     }
557
558     0?;
559   // ^
560     0xDEAD_BEEF
561  // ^^^^^^^^^^^
562 }
563 "#,
564         );
565     }
566
567     #[test]
568     fn test_hl_exit_points3() {
569         check(
570             r#"
571 fn$0 foo() -> u32 {
572     if true {
573         return 0;
574      // ^^^^^^
575     }
576
577     0?;
578   // ^
579     0xDEAD_BEEF
580  // ^^^^^^^^^^^
581 }
582 "#,
583         );
584     }
585
586     #[test]
587     fn test_hl_prefer_ref_over_tail_exit() {
588         check(
589             r#"
590 fn foo() -> u32 {
591 // ^^^
592     if true {
593         return 0;
594     }
595
596     0?;
597
598     foo$0()
599  // ^^^
600 }
601 "#,
602         );
603     }
604
605     #[test]
606     fn test_hl_never_call_is_exit_point() {
607         check(
608             r#"
609 struct Never;
610 impl Never {
611     fn never(self) -> ! { loop {} }
612 }
613 macro_rules! never {
614     () => { never() }
615 }
616 fn never() -> ! { loop {} }
617 fn foo() ->$0 u32 {
618     never();
619  // ^^^^^^^
620     never!();
621  // FIXME sema doesn't give us types for macrocalls
622
623     Never.never();
624  // ^^^^^^^^^^^^^
625
626     0
627  // ^
628 }
629 "#,
630         );
631     }
632
633     #[test]
634     fn test_hl_inner_tail_exit_points() {
635         check(
636             r#"
637 fn foo() ->$0 u32 {
638     if true {
639         unsafe {
640             return 5;
641          // ^^^^^^
642             5
643          // ^
644         }
645     } else if false {
646         0
647      // ^
648     } else {
649         match 5 {
650             6 => 100,
651               // ^^^
652             7 => loop {
653                 break 5;
654              // ^^^^^
655             }
656             8 => 'a: loop {
657                 'b: loop {
658                     break 'a 5;
659                  // ^^^^^
660                     break 'b 5;
661                     break 5;
662                 };
663             }
664             //
665             _ => 500,
666               // ^^^
667         }
668     }
669 }
670 "#,
671         );
672     }
673
674     #[test]
675     fn test_hl_inner_tail_exit_points_labeled_block() {
676         check(
677             r#"
678 fn foo() ->$0 u32 {
679     'foo: {
680         break 'foo 0;
681      // ^^^^^
682         loop {
683             break;
684             break 'foo 0;
685          // ^^^^^
686         }
687         0
688      // ^
689     }
690 }
691 "#,
692         );
693     }
694
695     #[test]
696     fn test_hl_break_loop() {
697         check(
698             r#"
699 fn foo() {
700     'outer: loop {
701  // ^^^^^^^^^^^^
702          break;
703       // ^^^^^
704          'inner: loop {
705             break;
706             'innermost: loop {
707                 break 'outer;
708              // ^^^^^^^^^^^^
709                 break 'inner;
710             }
711             break$0 'outer;
712          // ^^^^^^^^^^^^
713             break;
714         }
715         break;
716      // ^^^^^
717     }
718 }
719 "#,
720         );
721     }
722
723     #[test]
724     fn test_hl_break_loop2() {
725         check(
726             r#"
727 fn foo() {
728     'outer: loop {
729         break;
730         'inner: loop {
731      // ^^^^^^^^^^^^
732             break;
733          // ^^^^^
734             'innermost: loop {
735                 break 'outer;
736                 break 'inner;
737              // ^^^^^^^^^^^^
738             }
739             break 'outer;
740             break$0;
741          // ^^^^^
742         }
743         break;
744     }
745 }
746 "#,
747         );
748     }
749
750     #[test]
751     fn test_hl_break_for() {
752         check(
753             r#"
754 fn foo() {
755     'outer: for _ in () {
756  // ^^^^^^^^^^^
757          break;
758       // ^^^^^
759          'inner: for _ in () {
760             break;
761             'innermost: for _ in () {
762                 break 'outer;
763              // ^^^^^^^^^^^^
764                 break 'inner;
765             }
766             break$0 'outer;
767          // ^^^^^^^^^^^^
768             break;
769         }
770         break;
771      // ^^^^^
772     }
773 }
774 "#,
775         );
776     }
777
778     #[test]
779     fn test_hl_break_while() {
780         check(
781             r#"
782 fn foo() {
783     'outer: while true {
784  // ^^^^^^^^^^^^^
785          break;
786       // ^^^^^
787          'inner: while true {
788             break;
789             'innermost: while true {
790                 break 'outer;
791              // ^^^^^^^^^^^^
792                 break 'inner;
793             }
794             break$0 'outer;
795          // ^^^^^^^^^^^^
796             break;
797         }
798         break;
799      // ^^^^^
800     }
801 }
802 "#,
803         );
804     }
805
806     #[test]
807     fn test_hl_break_labeled_block() {
808         check(
809             r#"
810 fn foo() {
811     'outer: {
812  // ^^^^^^^
813          break;
814       // ^^^^^
815          'inner: {
816             break;
817             'innermost: {
818                 break 'outer;
819              // ^^^^^^^^^^^^
820                 break 'inner;
821             }
822             break$0 'outer;
823          // ^^^^^^^^^^^^
824             break;
825         }
826         break;
827      // ^^^^^
828     }
829 }
830 "#,
831         );
832     }
833
834     #[test]
835     fn test_hl_break_unlabeled_loop() {
836         check(
837             r#"
838 fn foo() {
839     loop {
840  // ^^^^
841         break$0;
842      // ^^^^^
843     }
844 }
845 "#,
846         );
847     }
848
849     #[test]
850     fn test_hl_break_unlabeled_block_in_loop() {
851         check(
852             r#"
853 fn foo() {
854     loop {
855  // ^^^^
856         {
857             break$0;
858          // ^^^^^
859         }
860     }
861 }
862 "#,
863         );
864     }
865
866     #[test]
867     fn test_hl_field_shorthand() {
868         check(
869             r#"
870 struct Struct { field: u32 }
871               //^^^^^
872 fn function(field: u32) {
873           //^^^^^
874     Struct { field$0 }
875            //^^^^^ read
876 }
877 "#,
878         );
879     }
880
881     #[test]
882     fn test_hl_disabled_ref_local() {
883         let config = HighlightRelatedConfig {
884             references: false,
885             break_points: true,
886             exit_points: true,
887             yield_points: true,
888         };
889
890         let ra_fixture = r#"
891 fn foo() {
892     let x$0 = 5;
893     let y = x * 2;
894 }"#;
895
896         check_with_config(ra_fixture, config);
897     }
898
899     #[test]
900     fn test_hl_disabled_ref_local_preserved_break() {
901         let config = HighlightRelatedConfig {
902             references: false,
903             break_points: true,
904             exit_points: true,
905             yield_points: true,
906         };
907
908         let ra_fixture = r#"
909 fn foo() {
910     let x$0 = 5;
911     let y = x * 2;
912
913     loop {
914         break;
915     }
916 }"#;
917
918         check_with_config(ra_fixture, config.clone());
919
920         let ra_fixture = r#"
921 fn foo() {
922     let x = 5;
923     let y = x * 2;
924
925     loop$0 {
926 //  ^^^^
927         break;
928 //      ^^^^^
929     }
930 }"#;
931
932         check_with_config(ra_fixture, config);
933     }
934
935     #[test]
936     fn test_hl_disabled_ref_local_preserved_yield() {
937         let config = HighlightRelatedConfig {
938             references: false,
939             break_points: true,
940             exit_points: true,
941             yield_points: true,
942         };
943
944         let ra_fixture = r#"
945 async fn foo() {
946     let x$0 = 5;
947     let y = x * 2;
948
949     0.await;
950 }"#;
951
952         check_with_config(ra_fixture, config.clone());
953
954         let ra_fixture = r#"
955     async fn foo() {
956 //  ^^^^^
957         let x = 5;
958         let y = x * 2;
959
960         0.await$0;
961 //        ^^^^^
962 }"#;
963
964         check_with_config(ra_fixture, config);
965     }
966
967     #[test]
968     fn test_hl_disabled_ref_local_preserved_exit() {
969         let config = HighlightRelatedConfig {
970             references: false,
971             break_points: true,
972             exit_points: true,
973             yield_points: true,
974         };
975
976         let ra_fixture = r#"
977 fn foo() -> i32 {
978     let x$0 = 5;
979     let y = x * 2;
980
981     if true {
982         return y;
983     }
984
985     0?
986 }"#;
987
988         check_with_config(ra_fixture, config.clone());
989
990         let ra_fixture = r#"
991 fn foo() ->$0 i32 {
992     let x = 5;
993     let y = x * 2;
994
995     if true {
996         return y;
997 //      ^^^^^^
998     }
999
1000     0?
1001 //   ^
1002 "#;
1003
1004         check_with_config(ra_fixture, config);
1005     }
1006
1007     #[test]
1008     fn test_hl_disabled_break() {
1009         let config = HighlightRelatedConfig {
1010             references: true,
1011             break_points: false,
1012             exit_points: true,
1013             yield_points: true,
1014         };
1015
1016         let ra_fixture = r#"
1017 fn foo() {
1018     loop {
1019         break$0;
1020     }
1021 }"#;
1022
1023         check_with_config(ra_fixture, config);
1024     }
1025
1026     #[test]
1027     fn test_hl_disabled_yield() {
1028         let config = HighlightRelatedConfig {
1029             references: true,
1030             break_points: true,
1031             exit_points: true,
1032             yield_points: false,
1033         };
1034
1035         let ra_fixture = r#"
1036 async$0 fn foo() {
1037     0.await;
1038 }"#;
1039
1040         check_with_config(ra_fixture, config);
1041     }
1042
1043     #[test]
1044     fn test_hl_disabled_exit() {
1045         let config = HighlightRelatedConfig {
1046             references: true,
1047             break_points: true,
1048             exit_points: false,
1049             yield_points: true,
1050         };
1051
1052         let ra_fixture = r#"
1053 fn foo() ->$0 i32 {
1054     if true {
1055         return -1;
1056     }
1057
1058     42
1059 }"#;
1060
1061         check_with_config(ra_fixture, config);
1062     }
1063 }