]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #10366
[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, node_ext::walk_expr, pick_best_token},
6     search::{FileReference, ReferenceAccess, SearchScope},
7     RootDatabase,
8 };
9 use rustc_hash::FxHashSet;
10 use syntax::{
11     ast::{self, HasLoopBody},
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         walk_expr(&body, &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::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
168                     hl(sema, Some(block_expr.into()))
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::StmtList>,
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::BlockExpr(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().and_then(|it| it.stmt_list()))
222             }
223             ast::Expr::ForExpr(f) if label_matches(f.label()) => {
224                 hl(f.for_token(), f.label(), f.loop_body().and_then(|it| it.stmt_list()))
225             }
226             ast::Expr::WhileExpr(w) if label_matches(w.label()) => {
227                 hl(w.while_token(), w.label(), w.loop_body().and_then(|it| it.stmt_list()))
228             }
229             ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => {
230                 hl(None, e.label(), e.stmt_list())
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             walk_expr(&body, &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::BlockExpr(block_expr) => {
262                     if block_expr.async_token().is_none() {
263                         continue;
264                     }
265                     hl(block_expr.async_token(), Some(block_expr.into()))
266                 },
267                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
268                 _ => continue,
269             }
270         };
271     }
272     None
273 }
274
275 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
276     match (r0, r1) {
277         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
278         (Some(range), None) => Some(range),
279         (None, Some(range)) => Some(range),
280         (None, None) => None,
281     }
282 }
283
284 fn find_defs(
285     sema: &Semantics<RootDatabase>,
286     syntax: &SyntaxNode,
287     offset: TextSize,
288 ) -> FxHashSet<Definition> {
289     sema.find_nodes_at_offset_with_descend(syntax, offset)
290         .flat_map(|name_like| {
291             Some(match name_like {
292                 ast::NameLike::NameRef(name_ref) => {
293                     match NameRefClass::classify(sema, &name_ref)? {
294                         NameRefClass::Definition(def) => vec![def],
295                         NameRefClass::FieldShorthand { local_ref, field_ref } => {
296                             vec![Definition::Local(local_ref), Definition::Field(field_ref)]
297                         }
298                     }
299                 }
300                 ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
301                     NameClass::Definition(it) | NameClass::ConstReference(it) => vec![it],
302                     NameClass::PatFieldShorthand { local_def, field_ref } => {
303                         vec![Definition::Local(local_def), Definition::Field(field_ref)]
304                     }
305                 },
306                 ast::NameLike::Lifetime(lifetime) => {
307                     NameRefClass::classify_lifetime(sema, &lifetime)
308                         .and_then(|class| match class {
309                             NameRefClass::Definition(it) => Some(it),
310                             _ => None,
311                         })
312                         .or_else(|| {
313                             NameClass::classify_lifetime(sema, &lifetime)
314                                 .and_then(NameClass::defined)
315                         })
316                         .map(|it| vec![it])?
317                 }
318             })
319         })
320         .flatten()
321         .collect()
322 }
323
324 #[cfg(test)]
325 mod tests {
326     use crate::fixture;
327
328     use super::*;
329
330     fn check(ra_fixture: &str) {
331         let config = HighlightRelatedConfig {
332             break_points: true,
333             exit_points: true,
334             references: true,
335             yield_points: true,
336         };
337
338         check_with_config(ra_fixture, config);
339     }
340
341     fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
342         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
343
344         let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or(Vec::default());
345
346         let mut expected = annotations
347             .into_iter()
348             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
349             .collect::<Vec<_>>();
350
351         let mut actual = hls
352             .into_iter()
353             .map(|hl| {
354                 (
355                     hl.range,
356                     hl.access.map(|it| {
357                         match it {
358                             ReferenceAccess::Read => "read",
359                             ReferenceAccess::Write => "write",
360                         }
361                         .to_string()
362                     }),
363                 )
364             })
365             .collect::<Vec<_>>();
366         actual.sort_by_key(|(range, _)| range.start());
367         expected.sort_by_key(|(range, _)| range.start());
368
369         assert_eq!(expected, actual);
370     }
371
372     #[test]
373     fn test_hl_module() {
374         check(
375             r#"
376 //- /lib.rs
377 mod foo$0;
378  // ^^^
379 //- /foo.rs
380 struct Foo;
381 "#,
382         );
383     }
384
385     #[test]
386     fn test_hl_self_in_crate_root() {
387         check(
388             r#"
389 use self$0;
390 "#,
391         );
392     }
393
394     #[test]
395     fn test_hl_self_in_module() {
396         check(
397             r#"
398 //- /lib.rs
399 mod foo;
400 //- /foo.rs
401 use self$0;
402  // ^^^^
403 "#,
404         );
405     }
406
407     #[test]
408     fn test_hl_local() {
409         check(
410             r#"
411 fn foo() {
412     let mut bar = 3;
413          // ^^^ write
414     bar$0;
415  // ^^^ read
416 }
417 "#,
418         );
419     }
420
421     #[test]
422     fn test_hl_local_in_attr() {
423         check(
424             r#"
425 //- proc_macros: identity
426 #[proc_macros::identity]
427 fn foo() {
428     let mut bar = 3;
429          // ^^^ write
430     bar$0;
431  // ^^^ read
432 }
433 "#,
434         );
435     }
436
437     #[test]
438     fn test_multi_macro_usage() {
439         check(
440             r#"
441 macro_rules! foo {
442     ($ident:ident) => {
443         fn $ident() -> $ident { loop {} }
444         struct $ident;
445     }
446 }
447
448 foo!(bar$0);
449   // ^^^
450 fn foo() {
451     let bar: bar = bar();
452           // ^^^
453                 // ^^^
454 }
455 "#,
456         );
457         check(
458             r#"
459 macro_rules! foo {
460     ($ident:ident) => {
461         fn $ident() -> $ident { loop {} }
462         struct $ident;
463     }
464 }
465
466 foo!(bar);
467   // ^^^
468 fn foo() {
469     let bar: bar$0 = bar();
470           // ^^^
471 }
472 "#,
473         );
474     }
475
476     #[test]
477     fn test_hl_yield_points() {
478         check(
479             r#"
480 pub async fn foo() {
481  // ^^^^^
482     let x = foo()
483         .await$0
484       // ^^^^^
485         .await;
486       // ^^^^^
487     || { 0.await };
488     (async { 0.await }).await
489                      // ^^^^^
490 }
491 "#,
492         );
493     }
494
495     #[test]
496     fn test_hl_yield_points2() {
497         check(
498             r#"
499 pub async$0 fn foo() {
500  // ^^^^^
501     let x = foo()
502         .await
503       // ^^^^^
504         .await;
505       // ^^^^^
506     || { 0.await };
507     (async { 0.await }).await
508                      // ^^^^^
509 }
510 "#,
511         );
512     }
513
514     #[test]
515     fn test_hl_yield_nested_fn() {
516         check(
517             r#"
518 async fn foo() {
519     async fn foo2() {
520  // ^^^^^
521         async fn foo3() {
522             0.await
523         }
524         0.await$0
525        // ^^^^^
526     }
527     0.await
528 }
529 "#,
530         );
531     }
532
533     #[test]
534     fn test_hl_yield_nested_async_blocks() {
535         check(
536             r#"
537 async fn foo() {
538     (async {
539   // ^^^^^
540         (async {
541            0.await
542         }).await$0 }
543         // ^^^^^
544     ).await;
545 }
546 "#,
547         );
548     }
549
550     #[test]
551     fn test_hl_exit_points() {
552         check(
553             r#"
554 fn foo() -> u32 {
555     if true {
556         return$0 0;
557      // ^^^^^^
558     }
559
560     0?;
561   // ^
562     0xDEAD_BEEF
563  // ^^^^^^^^^^^
564 }
565 "#,
566         );
567     }
568
569     #[test]
570     fn test_hl_exit_points2() {
571         check(
572             r#"
573 fn foo() ->$0 u32 {
574     if true {
575         return 0;
576      // ^^^^^^
577     }
578
579     0?;
580   // ^
581     0xDEAD_BEEF
582  // ^^^^^^^^^^^
583 }
584 "#,
585         );
586     }
587
588     #[test]
589     fn test_hl_exit_points3() {
590         check(
591             r#"
592 fn$0 foo() -> u32 {
593     if true {
594         return 0;
595      // ^^^^^^
596     }
597
598     0?;
599   // ^
600     0xDEAD_BEEF
601  // ^^^^^^^^^^^
602 }
603 "#,
604         );
605     }
606
607     #[test]
608     fn test_hl_prefer_ref_over_tail_exit() {
609         check(
610             r#"
611 fn foo() -> u32 {
612 // ^^^
613     if true {
614         return 0;
615     }
616
617     0?;
618
619     foo$0()
620  // ^^^
621 }
622 "#,
623         );
624     }
625
626     #[test]
627     fn test_hl_never_call_is_exit_point() {
628         check(
629             r#"
630 struct Never;
631 impl Never {
632     fn never(self) -> ! { loop {} }
633 }
634 macro_rules! never {
635     () => { never() }
636 }
637 fn never() -> ! { loop {} }
638 fn foo() ->$0 u32 {
639     never();
640  // ^^^^^^^
641     never!();
642  // FIXME sema doesn't give us types for macrocalls
643
644     Never.never();
645  // ^^^^^^^^^^^^^
646
647     0
648  // ^
649 }
650 "#,
651         );
652     }
653
654     #[test]
655     fn test_hl_inner_tail_exit_points() {
656         check(
657             r#"
658 fn foo() ->$0 u32 {
659     if true {
660         unsafe {
661             return 5;
662          // ^^^^^^
663             5
664          // ^
665         }
666     } else if false {
667         0
668      // ^
669     } else {
670         match 5 {
671             6 => 100,
672               // ^^^
673             7 => loop {
674                 break 5;
675              // ^^^^^
676             }
677             8 => 'a: loop {
678                 'b: loop {
679                     break 'a 5;
680                  // ^^^^^
681                     break 'b 5;
682                     break 5;
683                 };
684             }
685             //
686             _ => 500,
687               // ^^^
688         }
689     }
690 }
691 "#,
692         );
693     }
694
695     #[test]
696     fn test_hl_inner_tail_exit_points_labeled_block() {
697         check(
698             r#"
699 fn foo() ->$0 u32 {
700     'foo: {
701         break 'foo 0;
702      // ^^^^^
703         loop {
704             break;
705             break 'foo 0;
706          // ^^^^^
707         }
708         0
709      // ^
710     }
711 }
712 "#,
713         );
714     }
715
716     #[test]
717     fn test_hl_break_loop() {
718         check(
719             r#"
720 fn foo() {
721     'outer: loop {
722  // ^^^^^^^^^^^^
723          break;
724       // ^^^^^
725          'inner: loop {
726             break;
727             'innermost: loop {
728                 break 'outer;
729              // ^^^^^^^^^^^^
730                 break 'inner;
731             }
732             break$0 'outer;
733          // ^^^^^^^^^^^^
734             break;
735         }
736         break;
737      // ^^^^^
738     }
739 }
740 "#,
741         );
742     }
743
744     #[test]
745     fn test_hl_break_loop2() {
746         check(
747             r#"
748 fn foo() {
749     'outer: loop {
750         break;
751         'inner: loop {
752      // ^^^^^^^^^^^^
753             break;
754          // ^^^^^
755             'innermost: loop {
756                 break 'outer;
757                 break 'inner;
758              // ^^^^^^^^^^^^
759             }
760             break 'outer;
761             break$0;
762          // ^^^^^
763         }
764         break;
765     }
766 }
767 "#,
768         );
769     }
770
771     #[test]
772     fn test_hl_break_for() {
773         check(
774             r#"
775 fn foo() {
776     'outer: for _ in () {
777  // ^^^^^^^^^^^
778          break;
779       // ^^^^^
780          'inner: for _ in () {
781             break;
782             'innermost: for _ in () {
783                 break 'outer;
784              // ^^^^^^^^^^^^
785                 break 'inner;
786             }
787             break$0 'outer;
788          // ^^^^^^^^^^^^
789             break;
790         }
791         break;
792      // ^^^^^
793     }
794 }
795 "#,
796         );
797     }
798
799     #[test]
800     fn test_hl_break_while() {
801         check(
802             r#"
803 fn foo() {
804     'outer: while true {
805  // ^^^^^^^^^^^^^
806          break;
807       // ^^^^^
808          'inner: while true {
809             break;
810             'innermost: while true {
811                 break 'outer;
812              // ^^^^^^^^^^^^
813                 break 'inner;
814             }
815             break$0 'outer;
816          // ^^^^^^^^^^^^
817             break;
818         }
819         break;
820      // ^^^^^
821     }
822 }
823 "#,
824         );
825     }
826
827     #[test]
828     fn test_hl_break_labeled_block() {
829         check(
830             r#"
831 fn foo() {
832     'outer: {
833  // ^^^^^^^
834          break;
835       // ^^^^^
836          'inner: {
837             break;
838             'innermost: {
839                 break 'outer;
840              // ^^^^^^^^^^^^
841                 break 'inner;
842             }
843             break$0 'outer;
844          // ^^^^^^^^^^^^
845             break;
846         }
847         break;
848      // ^^^^^
849     }
850 }
851 "#,
852         );
853     }
854
855     #[test]
856     fn test_hl_break_unlabeled_loop() {
857         check(
858             r#"
859 fn foo() {
860     loop {
861  // ^^^^
862         break$0;
863      // ^^^^^
864     }
865 }
866 "#,
867         );
868     }
869
870     #[test]
871     fn test_hl_break_unlabeled_block_in_loop() {
872         check(
873             r#"
874 fn foo() {
875     loop {
876  // ^^^^
877         {
878             break$0;
879          // ^^^^^
880         }
881     }
882 }
883 "#,
884         );
885     }
886
887     #[test]
888     fn test_hl_field_shorthand() {
889         check(
890             r#"
891 struct Struct { field: u32 }
892               //^^^^^
893 fn function(field: u32) {
894           //^^^^^
895     Struct { field$0 }
896            //^^^^^ read
897 }
898 "#,
899         );
900     }
901
902     #[test]
903     fn test_hl_disabled_ref_local() {
904         let config = HighlightRelatedConfig {
905             references: false,
906             break_points: true,
907             exit_points: true,
908             yield_points: true,
909         };
910
911         let ra_fixture = r#"
912 fn foo() {
913     let x$0 = 5;
914     let y = x * 2;
915 }"#;
916
917         check_with_config(ra_fixture, config);
918     }
919
920     #[test]
921     fn test_hl_disabled_ref_local_preserved_break() {
922         let config = HighlightRelatedConfig {
923             references: false,
924             break_points: true,
925             exit_points: true,
926             yield_points: true,
927         };
928
929         let ra_fixture = r#"
930 fn foo() {
931     let x$0 = 5;
932     let y = x * 2;
933
934     loop {
935         break;
936     }
937 }"#;
938
939         check_with_config(ra_fixture, config.clone());
940
941         let ra_fixture = r#"
942 fn foo() {
943     let x = 5;
944     let y = x * 2;
945
946     loop$0 {
947 //  ^^^^
948         break;
949 //      ^^^^^
950     }
951 }"#;
952
953         check_with_config(ra_fixture, config);
954     }
955
956     #[test]
957     fn test_hl_disabled_ref_local_preserved_yield() {
958         let config = HighlightRelatedConfig {
959             references: false,
960             break_points: true,
961             exit_points: true,
962             yield_points: true,
963         };
964
965         let ra_fixture = r#"
966 async fn foo() {
967     let x$0 = 5;
968     let y = x * 2;
969
970     0.await;
971 }"#;
972
973         check_with_config(ra_fixture, config.clone());
974
975         let ra_fixture = r#"
976     async fn foo() {
977 //  ^^^^^
978         let x = 5;
979         let y = x * 2;
980
981         0.await$0;
982 //        ^^^^^
983 }"#;
984
985         check_with_config(ra_fixture, config);
986     }
987
988     #[test]
989     fn test_hl_disabled_ref_local_preserved_exit() {
990         let config = HighlightRelatedConfig {
991             references: false,
992             break_points: true,
993             exit_points: true,
994             yield_points: true,
995         };
996
997         let ra_fixture = r#"
998 fn foo() -> i32 {
999     let x$0 = 5;
1000     let y = x * 2;
1001
1002     if true {
1003         return y;
1004     }
1005
1006     0?
1007 }"#;
1008
1009         check_with_config(ra_fixture, config.clone());
1010
1011         let ra_fixture = r#"
1012 fn foo() ->$0 i32 {
1013     let x = 5;
1014     let y = x * 2;
1015
1016     if true {
1017         return y;
1018 //      ^^^^^^
1019     }
1020
1021     0?
1022 //   ^
1023 "#;
1024
1025         check_with_config(ra_fixture, config);
1026     }
1027
1028     #[test]
1029     fn test_hl_disabled_break() {
1030         let config = HighlightRelatedConfig {
1031             references: true,
1032             break_points: false,
1033             exit_points: true,
1034             yield_points: true,
1035         };
1036
1037         let ra_fixture = r#"
1038 fn foo() {
1039     loop {
1040         break$0;
1041     }
1042 }"#;
1043
1044         check_with_config(ra_fixture, config);
1045     }
1046
1047     #[test]
1048     fn test_hl_disabled_yield() {
1049         let config = HighlightRelatedConfig {
1050             references: true,
1051             break_points: true,
1052             exit_points: true,
1053             yield_points: false,
1054         };
1055
1056         let ra_fixture = r#"
1057 async$0 fn foo() {
1058     0.await;
1059 }"#;
1060
1061         check_with_config(ra_fixture, config);
1062     }
1063
1064     #[test]
1065     fn test_hl_disabled_exit() {
1066         let config = HighlightRelatedConfig {
1067             references: true,
1068             break_points: true,
1069             exit_points: false,
1070             yield_points: true,
1071         };
1072
1073         let ra_fixture = r#"
1074 fn foo() ->$0 i32 {
1075     if true {
1076         return -1;
1077     }
1078
1079     42
1080 }"#;
1081
1082         check_with_config(ra_fixture, config);
1083     }
1084 }