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