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