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