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