]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #10233
[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_hl_local_in_attr() {
418         check(
419             r#"
420 //- proc_macros: identity
421 #[proc_macros::identity]
422 fn foo() {
423     let mut bar = 3;
424          // ^^^ write
425     bar$0;
426  // ^^^ read
427 }
428 "#,
429         );
430     }
431
432     #[test]
433     fn test_multi_macro_usage() {
434         check(
435             r#"
436 macro_rules! foo {
437     ($ident:ident) => {
438         fn $ident() -> $ident { loop {} }
439         struct $ident;
440     }
441 }
442
443 foo!(bar$0);
444   // ^^^
445 fn foo() {
446     let bar: bar = bar();
447           // ^^^
448                 // ^^^
449 }
450 "#,
451         );
452         check(
453             r#"
454 macro_rules! foo {
455     ($ident:ident) => {
456         fn $ident() -> $ident { loop {} }
457         struct $ident;
458     }
459 }
460
461 foo!(bar);
462   // ^^^
463 fn foo() {
464     let bar: bar$0 = bar();
465           // ^^^
466 }
467 "#,
468         );
469     }
470
471     #[test]
472     fn test_hl_yield_points() {
473         check(
474             r#"
475 pub async fn foo() {
476  // ^^^^^
477     let x = foo()
478         .await$0
479       // ^^^^^
480         .await;
481       // ^^^^^
482     || { 0.await };
483     (async { 0.await }).await
484                      // ^^^^^
485 }
486 "#,
487         );
488     }
489
490     #[test]
491     fn test_hl_yield_points2() {
492         check(
493             r#"
494 pub async$0 fn foo() {
495  // ^^^^^
496     let x = foo()
497         .await
498       // ^^^^^
499         .await;
500       // ^^^^^
501     || { 0.await };
502     (async { 0.await }).await
503                      // ^^^^^
504 }
505 "#,
506         );
507     }
508
509     #[test]
510     fn test_hl_yield_nested_fn() {
511         check(
512             r#"
513 async fn foo() {
514     async fn foo2() {
515  // ^^^^^
516         async fn foo3() {
517             0.await
518         }
519         0.await$0
520        // ^^^^^
521     }
522     0.await
523 }
524 "#,
525         );
526     }
527
528     #[test]
529     fn test_hl_yield_nested_async_blocks() {
530         check(
531             r#"
532 async fn foo() {
533     (async {
534   // ^^^^^
535         (async {
536            0.await
537         }).await$0 }
538         // ^^^^^
539     ).await;
540 }
541 "#,
542         );
543     }
544
545     #[test]
546     fn test_hl_exit_points() {
547         check(
548             r#"
549 fn foo() -> u32 {
550     if true {
551         return$0 0;
552      // ^^^^^^
553     }
554
555     0?;
556   // ^
557     0xDEAD_BEEF
558  // ^^^^^^^^^^^
559 }
560 "#,
561         );
562     }
563
564     #[test]
565     fn test_hl_exit_points2() {
566         check(
567             r#"
568 fn foo() ->$0 u32 {
569     if true {
570         return 0;
571      // ^^^^^^
572     }
573
574     0?;
575   // ^
576     0xDEAD_BEEF
577  // ^^^^^^^^^^^
578 }
579 "#,
580         );
581     }
582
583     #[test]
584     fn test_hl_exit_points3() {
585         check(
586             r#"
587 fn$0 foo() -> u32 {
588     if true {
589         return 0;
590      // ^^^^^^
591     }
592
593     0?;
594   // ^
595     0xDEAD_BEEF
596  // ^^^^^^^^^^^
597 }
598 "#,
599         );
600     }
601
602     #[test]
603     fn test_hl_prefer_ref_over_tail_exit() {
604         check(
605             r#"
606 fn foo() -> u32 {
607 // ^^^
608     if true {
609         return 0;
610     }
611
612     0?;
613
614     foo$0()
615  // ^^^
616 }
617 "#,
618         );
619     }
620
621     #[test]
622     fn test_hl_never_call_is_exit_point() {
623         check(
624             r#"
625 struct Never;
626 impl Never {
627     fn never(self) -> ! { loop {} }
628 }
629 macro_rules! never {
630     () => { never() }
631 }
632 fn never() -> ! { loop {} }
633 fn foo() ->$0 u32 {
634     never();
635  // ^^^^^^^
636     never!();
637  // FIXME sema doesn't give us types for macrocalls
638
639     Never.never();
640  // ^^^^^^^^^^^^^
641
642     0
643  // ^
644 }
645 "#,
646         );
647     }
648
649     #[test]
650     fn test_hl_inner_tail_exit_points() {
651         check(
652             r#"
653 fn foo() ->$0 u32 {
654     if true {
655         unsafe {
656             return 5;
657          // ^^^^^^
658             5
659          // ^
660         }
661     } else if false {
662         0
663      // ^
664     } else {
665         match 5 {
666             6 => 100,
667               // ^^^
668             7 => loop {
669                 break 5;
670              // ^^^^^
671             }
672             8 => 'a: loop {
673                 'b: loop {
674                     break 'a 5;
675                  // ^^^^^
676                     break 'b 5;
677                     break 5;
678                 };
679             }
680             //
681             _ => 500,
682               // ^^^
683         }
684     }
685 }
686 "#,
687         );
688     }
689
690     #[test]
691     fn test_hl_inner_tail_exit_points_labeled_block() {
692         check(
693             r#"
694 fn foo() ->$0 u32 {
695     'foo: {
696         break 'foo 0;
697      // ^^^^^
698         loop {
699             break;
700             break 'foo 0;
701          // ^^^^^
702         }
703         0
704      // ^
705     }
706 }
707 "#,
708         );
709     }
710
711     #[test]
712     fn test_hl_break_loop() {
713         check(
714             r#"
715 fn foo() {
716     'outer: loop {
717  // ^^^^^^^^^^^^
718          break;
719       // ^^^^^
720          'inner: loop {
721             break;
722             'innermost: loop {
723                 break 'outer;
724              // ^^^^^^^^^^^^
725                 break 'inner;
726             }
727             break$0 'outer;
728          // ^^^^^^^^^^^^
729             break;
730         }
731         break;
732      // ^^^^^
733     }
734 }
735 "#,
736         );
737     }
738
739     #[test]
740     fn test_hl_break_loop2() {
741         check(
742             r#"
743 fn foo() {
744     'outer: loop {
745         break;
746         'inner: loop {
747      // ^^^^^^^^^^^^
748             break;
749          // ^^^^^
750             'innermost: loop {
751                 break 'outer;
752                 break 'inner;
753              // ^^^^^^^^^^^^
754             }
755             break 'outer;
756             break$0;
757          // ^^^^^
758         }
759         break;
760     }
761 }
762 "#,
763         );
764     }
765
766     #[test]
767     fn test_hl_break_for() {
768         check(
769             r#"
770 fn foo() {
771     'outer: for _ in () {
772  // ^^^^^^^^^^^
773          break;
774       // ^^^^^
775          'inner: for _ in () {
776             break;
777             'innermost: for _ in () {
778                 break 'outer;
779              // ^^^^^^^^^^^^
780                 break 'inner;
781             }
782             break$0 'outer;
783          // ^^^^^^^^^^^^
784             break;
785         }
786         break;
787      // ^^^^^
788     }
789 }
790 "#,
791         );
792     }
793
794     #[test]
795     fn test_hl_break_while() {
796         check(
797             r#"
798 fn foo() {
799     'outer: while true {
800  // ^^^^^^^^^^^^^
801          break;
802       // ^^^^^
803          'inner: while true {
804             break;
805             'innermost: while true {
806                 break 'outer;
807              // ^^^^^^^^^^^^
808                 break 'inner;
809             }
810             break$0 'outer;
811          // ^^^^^^^^^^^^
812             break;
813         }
814         break;
815      // ^^^^^
816     }
817 }
818 "#,
819         );
820     }
821
822     #[test]
823     fn test_hl_break_labeled_block() {
824         check(
825             r#"
826 fn foo() {
827     'outer: {
828  // ^^^^^^^
829          break;
830       // ^^^^^
831          'inner: {
832             break;
833             'innermost: {
834                 break 'outer;
835              // ^^^^^^^^^^^^
836                 break 'inner;
837             }
838             break$0 'outer;
839          // ^^^^^^^^^^^^
840             break;
841         }
842         break;
843      // ^^^^^
844     }
845 }
846 "#,
847         );
848     }
849
850     #[test]
851     fn test_hl_break_unlabeled_loop() {
852         check(
853             r#"
854 fn foo() {
855     loop {
856  // ^^^^
857         break$0;
858      // ^^^^^
859     }
860 }
861 "#,
862         );
863     }
864
865     #[test]
866     fn test_hl_break_unlabeled_block_in_loop() {
867         check(
868             r#"
869 fn foo() {
870     loop {
871  // ^^^^
872         {
873             break$0;
874          // ^^^^^
875         }
876     }
877 }
878 "#,
879         );
880     }
881
882     #[test]
883     fn test_hl_field_shorthand() {
884         check(
885             r#"
886 struct Struct { field: u32 }
887               //^^^^^
888 fn function(field: u32) {
889           //^^^^^
890     Struct { field$0 }
891            //^^^^^ read
892 }
893 "#,
894         );
895     }
896
897     #[test]
898     fn test_hl_disabled_ref_local() {
899         let config = HighlightRelatedConfig {
900             references: false,
901             break_points: true,
902             exit_points: true,
903             yield_points: true,
904         };
905
906         let ra_fixture = r#"
907 fn foo() {
908     let x$0 = 5;
909     let y = x * 2;
910 }"#;
911
912         check_with_config(ra_fixture, config);
913     }
914
915     #[test]
916     fn test_hl_disabled_ref_local_preserved_break() {
917         let config = HighlightRelatedConfig {
918             references: false,
919             break_points: true,
920             exit_points: true,
921             yield_points: true,
922         };
923
924         let ra_fixture = r#"
925 fn foo() {
926     let x$0 = 5;
927     let y = x * 2;
928
929     loop {
930         break;
931     }
932 }"#;
933
934         check_with_config(ra_fixture, config.clone());
935
936         let ra_fixture = r#"
937 fn foo() {
938     let x = 5;
939     let y = x * 2;
940
941     loop$0 {
942 //  ^^^^
943         break;
944 //      ^^^^^
945     }
946 }"#;
947
948         check_with_config(ra_fixture, config);
949     }
950
951     #[test]
952     fn test_hl_disabled_ref_local_preserved_yield() {
953         let config = HighlightRelatedConfig {
954             references: false,
955             break_points: true,
956             exit_points: true,
957             yield_points: true,
958         };
959
960         let ra_fixture = r#"
961 async fn foo() {
962     let x$0 = 5;
963     let y = x * 2;
964
965     0.await;
966 }"#;
967
968         check_with_config(ra_fixture, config.clone());
969
970         let ra_fixture = r#"
971     async fn foo() {
972 //  ^^^^^
973         let x = 5;
974         let y = x * 2;
975
976         0.await$0;
977 //        ^^^^^
978 }"#;
979
980         check_with_config(ra_fixture, config);
981     }
982
983     #[test]
984     fn test_hl_disabled_ref_local_preserved_exit() {
985         let config = HighlightRelatedConfig {
986             references: false,
987             break_points: true,
988             exit_points: true,
989             yield_points: true,
990         };
991
992         let ra_fixture = r#"
993 fn foo() -> i32 {
994     let x$0 = 5;
995     let y = x * 2;
996
997     if true {
998         return y;
999     }
1000
1001     0?
1002 }"#;
1003
1004         check_with_config(ra_fixture, config.clone());
1005
1006         let ra_fixture = r#"
1007 fn foo() ->$0 i32 {
1008     let x = 5;
1009     let y = x * 2;
1010
1011     if true {
1012         return y;
1013 //      ^^^^^^
1014     }
1015
1016     0?
1017 //   ^
1018 "#;
1019
1020         check_with_config(ra_fixture, config);
1021     }
1022
1023     #[test]
1024     fn test_hl_disabled_break() {
1025         let config = HighlightRelatedConfig {
1026             references: true,
1027             break_points: false,
1028             exit_points: true,
1029             yield_points: true,
1030         };
1031
1032         let ra_fixture = r#"
1033 fn foo() {
1034     loop {
1035         break$0;
1036     }
1037 }"#;
1038
1039         check_with_config(ra_fixture, config);
1040     }
1041
1042     #[test]
1043     fn test_hl_disabled_yield() {
1044         let config = HighlightRelatedConfig {
1045             references: true,
1046             break_points: true,
1047             exit_points: true,
1048             yield_points: false,
1049         };
1050
1051         let ra_fixture = r#"
1052 async$0 fn foo() {
1053     0.await;
1054 }"#;
1055
1056         check_with_config(ra_fixture, config);
1057     }
1058
1059     #[test]
1060     fn test_hl_disabled_exit() {
1061         let config = HighlightRelatedConfig {
1062             references: true,
1063             break_points: true,
1064             exit_points: false,
1065             yield_points: true,
1066         };
1067
1068         let ra_fixture = r#"
1069 fn foo() ->$0 i32 {
1070     if true {
1071         return -1;
1072     }
1073
1074     42
1075 }"#;
1076
1077         check_with_config(ra_fixture, config);
1078     }
1079 }