Skip to main content

ide/
annotations.rs

1use hir::{HasSource, InFile, InRealFile, Semantics};
2use ide_db::{
3    FileId, FilePosition, FileRange, FxIndexSet, RootDatabase, defs::Definition,
4    helpers::visit_file_defs, ra_fixture::RaFixtureConfig,
5};
6use itertools::Itertools;
7use syntax::{AstNode, TextRange, ast::HasName};
8
9use crate::{
10    NavigationTarget, RunnableKind,
11    annotations::fn_references::find_all_methods,
12    goto_implementation::{GotoImplementationConfig, goto_implementation},
13    navigation_target,
14    references::{FindAllRefsConfig, find_all_refs},
15    runnables::{Runnable, runnables},
16};
17
18mod fn_references;
19
20// Feature: Annotations
21//
22// Provides user with annotations above items for looking up references or impl blocks
23// and running/debugging binaries.
24//
25// ![Annotations](https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png)
26#[derive(Debug, Hash, PartialEq, Eq)]
27pub struct Annotation {
28    pub range: TextRange,
29    pub kind: AnnotationKind,
30}
31
32#[derive(Debug, Hash, PartialEq, Eq)]
33pub enum AnnotationKind {
34    Runnable(Runnable),
35    HasImpls { pos: FilePosition, data: Option<Vec<NavigationTarget>> },
36    HasReferences { pos: FilePosition, data: Option<Vec<FileRange>> },
37}
38
39pub struct AnnotationConfig<'a> {
40    pub binary_target: bool,
41    pub annotate_runnables: bool,
42    pub annotate_impls: bool,
43    pub annotate_references: bool,
44    pub annotate_method_references: bool,
45    pub annotate_enum_variant_references: bool,
46    pub references_exclude_imports: bool,
47    pub references_exclude_tests: bool,
48    pub location: AnnotationLocation,
49    pub filter_adjacent_derive_implementations: bool,
50    pub ra_fixture: RaFixtureConfig<'a>,
51}
52
53pub enum AnnotationLocation {
54    AboveName,
55    AboveWholeItem,
56}
57
58pub(crate) fn annotations(
59    db: &RootDatabase,
60    config: &AnnotationConfig<'_>,
61    file_id: FileId,
62) -> Vec<Annotation> {
63    let mut annotations = FxIndexSet::default();
64
65    if config.annotate_runnables {
66        for runnable in runnables(db, file_id) {
67            if should_skip_runnable(&runnable.kind, config.binary_target) {
68                continue;
69            }
70
71            let range = runnable.nav.focus_or_full_range();
72
73            annotations.insert(Annotation { range, kind: AnnotationKind::Runnable(runnable) });
74        }
75    }
76
77    let mk_ranges = |(range, focus): (_, Option<_>)| {
78        let cmd_target: TextRange = focus.unwrap_or(range);
79        let annotation_range = match config.location {
80            AnnotationLocation::AboveName => cmd_target,
81            AnnotationLocation::AboveWholeItem => range,
82        };
83        let target_pos = FilePosition { file_id, offset: cmd_target.start() };
84        (annotation_range, target_pos)
85    };
86
87    visit_file_defs(&Semantics::new(db), file_id, &mut |def| {
88        let range = match def {
89            Definition::Const(konst) if config.annotate_references => {
90                konst.source(db).and_then(|node| name_range(db, node, file_id))
91            }
92            Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => {
93                trait_.source(db).and_then(|node| name_range(db, node, file_id))
94            }
95            Definition::Adt(adt) => match adt {
96                hir::Adt::Enum(enum_) => {
97                    if config.annotate_enum_variant_references {
98                        enum_
99                            .variants(db)
100                            .into_iter()
101                            .filter_map(|variant| {
102                                variant.source(db).and_then(|node| name_range(db, node, file_id))
103                            })
104                            .for_each(|range| {
105                                let (annotation_range, target_position) = mk_ranges(range);
106                                annotations.insert(Annotation {
107                                    range: annotation_range,
108                                    kind: AnnotationKind::HasReferences {
109                                        pos: target_position,
110                                        data: None,
111                                    },
112                                });
113                            })
114                    }
115                    if config.annotate_references || config.annotate_impls {
116                        enum_.source(db).and_then(|node| name_range(db, node, file_id))
117                    } else {
118                        None
119                    }
120                }
121                _ => {
122                    if config.annotate_references || config.annotate_impls {
123                        adt.source(db).and_then(|node| name_range(db, node, file_id))
124                    } else {
125                        None
126                    }
127                }
128            },
129            _ => None,
130        };
131
132        let range = match range {
133            Some(range) => range,
134            None => return,
135        };
136        let (annotation_range, target_pos) = mk_ranges(range);
137        if config.annotate_impls && !matches!(def, Definition::Const(_)) {
138            annotations.insert(Annotation {
139                range: annotation_range,
140                kind: AnnotationKind::HasImpls { pos: target_pos, data: None },
141            });
142        }
143
144        if config.annotate_references {
145            annotations.insert(Annotation {
146                range: annotation_range,
147                kind: AnnotationKind::HasReferences { pos: target_pos, data: None },
148            });
149        }
150
151        fn name_range<T: HasName>(
152            db: &RootDatabase,
153            node: InFile<T>,
154            source_file_id: FileId,
155        ) -> Option<(TextRange, Option<TextRange>)> {
156            if let Some(name) = node.value.name().map(|name| name.syntax().text_range()) {
157                // if we have a name, try mapping that out of the macro expansion as we can put the
158                // annotation on that name token
159                // See `test_no_annotations_macro_struct_def` vs `test_annotations_macro_struct_def_call_site`
160                let res = navigation_target::orig_range_with_focus_r(
161                    db,
162                    node.file_id,
163                    node.value.syntax().text_range(),
164                    Some(name),
165                );
166                if res.call_site.0.file_id == source_file_id
167                    && let Some(name_range) = res.call_site.1
168                {
169                    return Some((res.call_site.0.range, Some(name_range)));
170                }
171            };
172            // otherwise try upmapping the entire node out of attributes
173            let InRealFile { file_id, value } = node.original_ast_node_rooted(db)?;
174            if file_id.file_id(db) == source_file_id {
175                Some((
176                    value.syntax().text_range(),
177                    value.name().map(|name| name.syntax().text_range()),
178                ))
179            } else {
180                None
181            }
182        }
183    });
184
185    if config.annotate_method_references {
186        annotations.extend(find_all_methods(db, file_id).into_iter().map(|range| {
187            let (annotation_range, target_range) = mk_ranges(range);
188            Annotation {
189                range: annotation_range,
190                kind: AnnotationKind::HasReferences { pos: target_range, data: None },
191            }
192        }));
193    }
194
195    annotations
196        .into_iter()
197        .sorted_by_key(|a| {
198            (a.range.start(), a.range.end(), matches!(a.kind, AnnotationKind::Runnable(..)))
199        })
200        .collect()
201}
202
203pub(crate) fn resolve_annotation(
204    db: &RootDatabase,
205    config: &AnnotationConfig<'_>,
206    mut annotation: Annotation,
207) -> Annotation {
208    match annotation.kind {
209        AnnotationKind::HasImpls { pos, ref mut data } => {
210            let goto_implementation_config = GotoImplementationConfig {
211                filter_adjacent_derive_implementations: config
212                    .filter_adjacent_derive_implementations,
213            };
214            *data =
215                goto_implementation(db, &goto_implementation_config, pos).map(|range| range.info);
216        }
217        AnnotationKind::HasReferences { pos, ref mut data } => {
218            *data = find_all_refs(
219                &Semantics::new(db),
220                pos,
221                &FindAllRefsConfig {
222                    search_scope: None,
223                    ra_fixture: config.ra_fixture,
224                    exclude_imports: config.references_exclude_imports,
225                    exclude_tests: config.references_exclude_tests,
226                },
227            )
228            .map(|result| {
229                result
230                    .into_iter()
231                    .flat_map(|res| res.references)
232                    .flat_map(|(file_id, access)| {
233                        access.into_iter().map(move |(range, _)| FileRange { file_id, range })
234                    })
235                    .collect()
236            });
237        }
238        _ => {}
239    };
240
241    annotation
242}
243
244fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
245    match kind {
246        RunnableKind::Bin => !binary_target,
247        _ => false,
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use expect_test::{Expect, expect};
254    use ide_db::ra_fixture::RaFixtureConfig;
255
256    use crate::{Annotation, AnnotationConfig, fixture};
257
258    use super::AnnotationLocation;
259
260    const DEFAULT_CONFIG: AnnotationConfig<'_> = AnnotationConfig {
261        binary_target: true,
262        annotate_runnables: true,
263        annotate_impls: true,
264        annotate_references: true,
265        annotate_method_references: true,
266        annotate_enum_variant_references: true,
267        references_exclude_imports: false,
268        references_exclude_tests: false,
269        location: AnnotationLocation::AboveName,
270        ra_fixture: RaFixtureConfig::default(),
271        filter_adjacent_derive_implementations: false,
272    };
273
274    fn check_with_config(
275        #[rust_analyzer::rust_fixture] ra_fixture: &str,
276        expect: Expect,
277        config: &AnnotationConfig<'_>,
278    ) {
279        let (analysis, file_id) = fixture::file(ra_fixture);
280
281        let annotations: Vec<Annotation> = analysis
282            .annotations(config, file_id)
283            .unwrap()
284            .into_iter()
285            .map(|annotation| analysis.resolve_annotation(config, annotation).unwrap())
286            .collect();
287
288        expect.assert_debug_eq(&annotations);
289    }
290
291    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
292        check_with_config(ra_fixture, expect, &DEFAULT_CONFIG);
293    }
294
295    #[test]
296    fn const_annotations() {
297        check(
298            r#"
299const DEMO: i32 = 123;
300
301const UNUSED: i32 = 123;
302
303fn main() {
304    let hello = DEMO;
305}
306            "#,
307            expect![[r#"
308                [
309                    Annotation {
310                        range: 6..10,
311                        kind: HasReferences {
312                            pos: FilePositionWrapper {
313                                file_id: FileId(
314                                    0,
315                                ),
316                                offset: 6,
317                            },
318                            data: Some(
319                                [
320                                    FileRangeWrapper {
321                                        file_id: FileId(
322                                            0,
323                                        ),
324                                        range: 78..82,
325                                    },
326                                ],
327                            ),
328                        },
329                    },
330                    Annotation {
331                        range: 30..36,
332                        kind: HasReferences {
333                            pos: FilePositionWrapper {
334                                file_id: FileId(
335                                    0,
336                                ),
337                                offset: 30,
338                            },
339                            data: Some(
340                                [],
341                            ),
342                        },
343                    },
344                    Annotation {
345                        range: 53..57,
346                        kind: HasReferences {
347                            pos: FilePositionWrapper {
348                                file_id: FileId(
349                                    0,
350                                ),
351                                offset: 53,
352                            },
353                            data: Some(
354                                [],
355                            ),
356                        },
357                    },
358                    Annotation {
359                        range: 53..57,
360                        kind: Runnable(
361                            Runnable {
362                                use_name_in_title: false,
363                                nav: NavigationTarget {
364                                    file_id: FileId(
365                                        0,
366                                    ),
367                                    full_range: 50..85,
368                                    focus_range: 53..57,
369                                    name: "main",
370                                    kind: Function,
371                                },
372                                kind: Bin,
373                                cfg: None,
374                                update_test: UpdateTest {
375                                    expect_test: false,
376                                    insta: false,
377                                    snapbox: false,
378                                },
379                            },
380                        ),
381                    },
382                ]
383            "#]],
384        );
385    }
386
387    #[test]
388    fn struct_references_annotations() {
389        check(
390            r#"
391struct Test;
392
393fn main() {
394    let test = Test;
395}
396            "#,
397            expect![[r#"
398                [
399                    Annotation {
400                        range: 7..11,
401                        kind: HasImpls {
402                            pos: FilePositionWrapper {
403                                file_id: FileId(
404                                    0,
405                                ),
406                                offset: 7,
407                            },
408                            data: Some(
409                                [],
410                            ),
411                        },
412                    },
413                    Annotation {
414                        range: 7..11,
415                        kind: HasReferences {
416                            pos: FilePositionWrapper {
417                                file_id: FileId(
418                                    0,
419                                ),
420                                offset: 7,
421                            },
422                            data: Some(
423                                [
424                                    FileRangeWrapper {
425                                        file_id: FileId(
426                                            0,
427                                        ),
428                                        range: 41..45,
429                                    },
430                                ],
431                            ),
432                        },
433                    },
434                    Annotation {
435                        range: 17..21,
436                        kind: HasReferences {
437                            pos: FilePositionWrapper {
438                                file_id: FileId(
439                                    0,
440                                ),
441                                offset: 17,
442                            },
443                            data: Some(
444                                [],
445                            ),
446                        },
447                    },
448                    Annotation {
449                        range: 17..21,
450                        kind: Runnable(
451                            Runnable {
452                                use_name_in_title: false,
453                                nav: NavigationTarget {
454                                    file_id: FileId(
455                                        0,
456                                    ),
457                                    full_range: 14..48,
458                                    focus_range: 17..21,
459                                    name: "main",
460                                    kind: Function,
461                                },
462                                kind: Bin,
463                                cfg: None,
464                                update_test: UpdateTest {
465                                    expect_test: false,
466                                    insta: false,
467                                    snapbox: false,
468                                },
469                            },
470                        ),
471                    },
472                ]
473            "#]],
474        );
475    }
476
477    #[test]
478    fn struct_and_trait_impls_annotations() {
479        check(
480            r#"
481struct Test;
482
483trait MyCoolTrait {}
484
485impl MyCoolTrait for Test {}
486
487fn main() {
488    let test = Test;
489}
490            "#,
491            expect![[r#"
492                [
493                    Annotation {
494                        range: 7..11,
495                        kind: HasImpls {
496                            pos: FilePositionWrapper {
497                                file_id: FileId(
498                                    0,
499                                ),
500                                offset: 7,
501                            },
502                            data: Some(
503                                [
504                                    NavigationTarget {
505                                        file_id: FileId(
506                                            0,
507                                        ),
508                                        full_range: 36..64,
509                                        focus_range: 57..61,
510                                        name: "impl",
511                                        kind: Impl,
512                                    },
513                                ],
514                            ),
515                        },
516                    },
517                    Annotation {
518                        range: 7..11,
519                        kind: HasReferences {
520                            pos: FilePositionWrapper {
521                                file_id: FileId(
522                                    0,
523                                ),
524                                offset: 7,
525                            },
526                            data: Some(
527                                [
528                                    FileRangeWrapper {
529                                        file_id: FileId(
530                                            0,
531                                        ),
532                                        range: 57..61,
533                                    },
534                                    FileRangeWrapper {
535                                        file_id: FileId(
536                                            0,
537                                        ),
538                                        range: 93..97,
539                                    },
540                                ],
541                            ),
542                        },
543                    },
544                    Annotation {
545                        range: 20..31,
546                        kind: HasImpls {
547                            pos: FilePositionWrapper {
548                                file_id: FileId(
549                                    0,
550                                ),
551                                offset: 20,
552                            },
553                            data: Some(
554                                [
555                                    NavigationTarget {
556                                        file_id: FileId(
557                                            0,
558                                        ),
559                                        full_range: 36..64,
560                                        focus_range: 57..61,
561                                        name: "impl",
562                                        kind: Impl,
563                                    },
564                                ],
565                            ),
566                        },
567                    },
568                    Annotation {
569                        range: 20..31,
570                        kind: HasReferences {
571                            pos: FilePositionWrapper {
572                                file_id: FileId(
573                                    0,
574                                ),
575                                offset: 20,
576                            },
577                            data: Some(
578                                [
579                                    FileRangeWrapper {
580                                        file_id: FileId(
581                                            0,
582                                        ),
583                                        range: 41..52,
584                                    },
585                                ],
586                            ),
587                        },
588                    },
589                    Annotation {
590                        range: 69..73,
591                        kind: HasReferences {
592                            pos: FilePositionWrapper {
593                                file_id: FileId(
594                                    0,
595                                ),
596                                offset: 69,
597                            },
598                            data: Some(
599                                [],
600                            ),
601                        },
602                    },
603                    Annotation {
604                        range: 69..73,
605                        kind: Runnable(
606                            Runnable {
607                                use_name_in_title: false,
608                                nav: NavigationTarget {
609                                    file_id: FileId(
610                                        0,
611                                    ),
612                                    full_range: 66..100,
613                                    focus_range: 69..73,
614                                    name: "main",
615                                    kind: Function,
616                                },
617                                kind: Bin,
618                                cfg: None,
619                                update_test: UpdateTest {
620                                    expect_test: false,
621                                    insta: false,
622                                    snapbox: false,
623                                },
624                            },
625                        ),
626                    },
627                ]
628            "#]],
629        );
630    }
631
632    #[test]
633    fn runnable_annotation() {
634        check(
635            r#"
636fn main() {}
637            "#,
638            expect![[r#"
639                [
640                    Annotation {
641                        range: 3..7,
642                        kind: HasReferences {
643                            pos: FilePositionWrapper {
644                                file_id: FileId(
645                                    0,
646                                ),
647                                offset: 3,
648                            },
649                            data: Some(
650                                [],
651                            ),
652                        },
653                    },
654                    Annotation {
655                        range: 3..7,
656                        kind: Runnable(
657                            Runnable {
658                                use_name_in_title: false,
659                                nav: NavigationTarget {
660                                    file_id: FileId(
661                                        0,
662                                    ),
663                                    full_range: 0..12,
664                                    focus_range: 3..7,
665                                    name: "main",
666                                    kind: Function,
667                                },
668                                kind: Bin,
669                                cfg: None,
670                                update_test: UpdateTest {
671                                    expect_test: false,
672                                    insta: false,
673                                    snapbox: false,
674                                },
675                            },
676                        ),
677                    },
678                ]
679            "#]],
680        );
681    }
682
683    #[test]
684    fn method_annotations() {
685        check(
686            r#"
687struct Test;
688
689impl Test {
690    fn self_by_ref(&self) {}
691}
692
693fn main() {
694    Test.self_by_ref();
695}
696            "#,
697            expect![[r#"
698                [
699                    Annotation {
700                        range: 7..11,
701                        kind: HasImpls {
702                            pos: FilePositionWrapper {
703                                file_id: FileId(
704                                    0,
705                                ),
706                                offset: 7,
707                            },
708                            data: Some(
709                                [
710                                    NavigationTarget {
711                                        file_id: FileId(
712                                            0,
713                                        ),
714                                        full_range: 14..56,
715                                        focus_range: 19..23,
716                                        name: "impl",
717                                        kind: Impl,
718                                    },
719                                ],
720                            ),
721                        },
722                    },
723                    Annotation {
724                        range: 7..11,
725                        kind: HasReferences {
726                            pos: FilePositionWrapper {
727                                file_id: FileId(
728                                    0,
729                                ),
730                                offset: 7,
731                            },
732                            data: Some(
733                                [
734                                    FileRangeWrapper {
735                                        file_id: FileId(
736                                            0,
737                                        ),
738                                        range: 19..23,
739                                    },
740                                    FileRangeWrapper {
741                                        file_id: FileId(
742                                            0,
743                                        ),
744                                        range: 74..78,
745                                    },
746                                ],
747                            ),
748                        },
749                    },
750                    Annotation {
751                        range: 33..44,
752                        kind: HasReferences {
753                            pos: FilePositionWrapper {
754                                file_id: FileId(
755                                    0,
756                                ),
757                                offset: 33,
758                            },
759                            data: Some(
760                                [
761                                    FileRangeWrapper {
762                                        file_id: FileId(
763                                            0,
764                                        ),
765                                        range: 79..90,
766                                    },
767                                ],
768                            ),
769                        },
770                    },
771                    Annotation {
772                        range: 61..65,
773                        kind: HasReferences {
774                            pos: FilePositionWrapper {
775                                file_id: FileId(
776                                    0,
777                                ),
778                                offset: 61,
779                            },
780                            data: Some(
781                                [],
782                            ),
783                        },
784                    },
785                    Annotation {
786                        range: 61..65,
787                        kind: Runnable(
788                            Runnable {
789                                use_name_in_title: false,
790                                nav: NavigationTarget {
791                                    file_id: FileId(
792                                        0,
793                                    ),
794                                    full_range: 58..95,
795                                    focus_range: 61..65,
796                                    name: "main",
797                                    kind: Function,
798                                },
799                                kind: Bin,
800                                cfg: None,
801                                update_test: UpdateTest {
802                                    expect_test: false,
803                                    insta: false,
804                                    snapbox: false,
805                                },
806                            },
807                        ),
808                    },
809                ]
810            "#]],
811        );
812    }
813
814    #[test]
815    fn test_annotations() {
816        check(
817            r#"
818fn main() {}
819
820mod tests {
821    #[test]
822    fn my_cool_test() {}
823}
824            "#,
825            expect![[r#"
826                [
827                    Annotation {
828                        range: 3..7,
829                        kind: HasReferences {
830                            pos: FilePositionWrapper {
831                                file_id: FileId(
832                                    0,
833                                ),
834                                offset: 3,
835                            },
836                            data: Some(
837                                [],
838                            ),
839                        },
840                    },
841                    Annotation {
842                        range: 3..7,
843                        kind: Runnable(
844                            Runnable {
845                                use_name_in_title: false,
846                                nav: NavigationTarget {
847                                    file_id: FileId(
848                                        0,
849                                    ),
850                                    full_range: 0..12,
851                                    focus_range: 3..7,
852                                    name: "main",
853                                    kind: Function,
854                                },
855                                kind: Bin,
856                                cfg: None,
857                                update_test: UpdateTest {
858                                    expect_test: false,
859                                    insta: false,
860                                    snapbox: false,
861                                },
862                            },
863                        ),
864                    },
865                    Annotation {
866                        range: 18..23,
867                        kind: Runnable(
868                            Runnable {
869                                use_name_in_title: false,
870                                nav: NavigationTarget {
871                                    file_id: FileId(
872                                        0,
873                                    ),
874                                    full_range: 14..64,
875                                    focus_range: 18..23,
876                                    name: "tests",
877                                    kind: Module,
878                                    description: "mod tests",
879                                },
880                                kind: TestMod {
881                                    path: "tests",
882                                },
883                                cfg: None,
884                                update_test: UpdateTest {
885                                    expect_test: false,
886                                    insta: false,
887                                    snapbox: false,
888                                },
889                            },
890                        ),
891                    },
892                    Annotation {
893                        range: 45..57,
894                        kind: Runnable(
895                            Runnable {
896                                use_name_in_title: false,
897                                nav: NavigationTarget {
898                                    file_id: FileId(
899                                        0,
900                                    ),
901                                    full_range: 30..62,
902                                    focus_range: 45..57,
903                                    name: "my_cool_test",
904                                    kind: Function,
905                                },
906                                kind: Test {
907                                    test_id: Path(
908                                        "tests::my_cool_test",
909                                    ),
910                                },
911                                cfg: None,
912                                update_test: UpdateTest {
913                                    expect_test: false,
914                                    insta: false,
915                                    snapbox: false,
916                                },
917                            },
918                        ),
919                    },
920                ]
921            "#]],
922        );
923    }
924
925    #[test]
926    fn test_no_annotations_outside_module_tree() {
927        check(
928            r#"
929//- /foo.rs
930struct Foo;
931//- /lib.rs
932// this file comes last since `check` checks the first file only
933"#,
934            expect![[r#"
935                []
936            "#]],
937        );
938    }
939
940    #[test]
941    fn test_no_annotations_macro_struct_def() {
942        check(
943            r#"
944//- /lib.rs
945macro_rules! m {
946    () => {
947        struct A {}
948    };
949}
950
951m!();
952"#,
953            expect![[r#"
954                []
955            "#]],
956        );
957    }
958
959    #[test]
960    fn test_annotations_macro_struct_def_call_site() {
961        check(
962            r#"
963//- /lib.rs
964macro_rules! m {
965    ($name:ident) => {
966        struct $name {}
967    };
968}
969
970m! {
971    Name
972};
973"#,
974            expect![[r#"
975                [
976                    Annotation {
977                        range: 83..87,
978                        kind: HasImpls {
979                            pos: FilePositionWrapper {
980                                file_id: FileId(
981                                    0,
982                                ),
983                                offset: 83,
984                            },
985                            data: Some(
986                                [],
987                            ),
988                        },
989                    },
990                    Annotation {
991                        range: 83..87,
992                        kind: HasReferences {
993                            pos: FilePositionWrapper {
994                                file_id: FileId(
995                                    0,
996                                ),
997                                offset: 83,
998                            },
999                            data: Some(
1000                                [],
1001                            ),
1002                        },
1003                    },
1004                ]
1005            "#]],
1006        );
1007    }
1008
1009    #[test]
1010    fn test_annotations_appear_above_whole_item_when_configured_to_do_so() {
1011        check_with_config(
1012            r#"
1013/// This is a struct named Foo, obviously.
1014#[derive(Clone)]
1015struct Foo;
1016"#,
1017            expect![[r#"
1018                [
1019                    Annotation {
1020                        range: 0..71,
1021                        kind: HasImpls {
1022                            pos: FilePositionWrapper {
1023                                file_id: FileId(
1024                                    0,
1025                                ),
1026                                offset: 67,
1027                            },
1028                            data: Some(
1029                                [],
1030                            ),
1031                        },
1032                    },
1033                    Annotation {
1034                        range: 0..71,
1035                        kind: HasReferences {
1036                            pos: FilePositionWrapper {
1037                                file_id: FileId(
1038                                    0,
1039                                ),
1040                                offset: 67,
1041                            },
1042                            data: Some(
1043                                [],
1044                            ),
1045                        },
1046                    },
1047                ]
1048            "#]],
1049            &AnnotationConfig { location: AnnotationLocation::AboveWholeItem, ..DEFAULT_CONFIG },
1050        );
1051    }
1052
1053    #[test]
1054    fn refs_exclude_tests() {
1055        check_with_config(
1056            r#"
1057fn foo() {}
1058
1059#[test]
1060fn bar() { foo() }
1061            "#,
1062            expect![[r#"
1063                [
1064                    Annotation {
1065                        range: 3..6,
1066                        kind: HasReferences {
1067                            pos: FilePositionWrapper {
1068                                file_id: FileId(
1069                                    0,
1070                                ),
1071                                offset: 3,
1072                            },
1073                            data: Some(
1074                                [],
1075                            ),
1076                        },
1077                    },
1078                ]
1079            "#]],
1080            &AnnotationConfig {
1081                references_exclude_tests: true,
1082                annotate_runnables: false,
1083                ..DEFAULT_CONFIG
1084            },
1085        );
1086    }
1087}