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