]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/annotations.rs
Merge #11877
[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                             .flatten()
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                     .flat_map(|(file_id, access)| {
174                         access.into_iter().map(move |(range, _)| FileRange { file_id, range })
175                     })
176                     .collect()
177             });
178         }
179         _ => {}
180     };
181
182     annotation
183 }
184
185 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
186     match kind {
187         RunnableKind::Bin => !binary_target,
188         _ => false,
189     }
190 }
191
192 #[cfg(test)]
193 mod tests {
194     use expect_test::{expect, Expect};
195
196     use crate::{fixture, Annotation, AnnotationConfig};
197
198     fn check(ra_fixture: &str, expect: Expect) {
199         let (analysis, file_id) = fixture::file(ra_fixture);
200
201         let annotations: Vec<Annotation> = analysis
202             .annotations(
203                 &AnnotationConfig {
204                     binary_target: true,
205                     annotate_runnables: true,
206                     annotate_impls: true,
207                     annotate_references: true,
208                     annotate_method_references: true,
209                     annotate_enum_variant_references: true,
210                 },
211                 file_id,
212             )
213             .unwrap()
214             .into_iter()
215             .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
216             .collect();
217
218         expect.assert_debug_eq(&annotations);
219     }
220
221     #[test]
222     fn const_annotations() {
223         check(
224             r#"
225 const DEMO: i32 = 123;
226
227 const UNUSED: i32 = 123;
228
229 fn main() {
230     let hello = DEMO;
231 }
232             "#,
233             expect![[r#"
234                 [
235                     Annotation {
236                         range: 53..57,
237                         kind: Runnable(
238                             Runnable {
239                                 use_name_in_title: false,
240                                 nav: NavigationTarget {
241                                     file_id: FileId(
242                                         0,
243                                     ),
244                                     full_range: 50..85,
245                                     focus_range: 53..57,
246                                     name: "main",
247                                     kind: Function,
248                                 },
249                                 kind: Bin,
250                                 cfg: None,
251                             },
252                         ),
253                     },
254                     Annotation {
255                         range: 6..10,
256                         kind: HasReferences {
257                             position: FilePosition {
258                                 file_id: FileId(
259                                     0,
260                                 ),
261                                 offset: 6,
262                             },
263                             data: Some(
264                                 [
265                                     FileRange {
266                                         file_id: FileId(
267                                             0,
268                                         ),
269                                         range: 78..82,
270                                     },
271                                 ],
272                             ),
273                         },
274                     },
275                     Annotation {
276                         range: 30..36,
277                         kind: HasReferences {
278                             position: FilePosition {
279                                 file_id: FileId(
280                                     0,
281                                 ),
282                                 offset: 30,
283                             },
284                             data: Some(
285                                 [],
286                             ),
287                         },
288                     },
289                     Annotation {
290                         range: 53..57,
291                         kind: HasReferences {
292                             position: FilePosition {
293                                 file_id: FileId(
294                                     0,
295                                 ),
296                                 offset: 53,
297                             },
298                             data: Some(
299                                 [],
300                             ),
301                         },
302                     },
303                 ]
304             "#]],
305         );
306     }
307
308     #[test]
309     fn struct_references_annotations() {
310         check(
311             r#"
312 struct Test;
313
314 fn main() {
315     let test = Test;
316 }
317             "#,
318             expect![[r#"
319                 [
320                     Annotation {
321                         range: 17..21,
322                         kind: Runnable(
323                             Runnable {
324                                 use_name_in_title: false,
325                                 nav: NavigationTarget {
326                                     file_id: FileId(
327                                         0,
328                                     ),
329                                     full_range: 14..48,
330                                     focus_range: 17..21,
331                                     name: "main",
332                                     kind: Function,
333                                 },
334                                 kind: Bin,
335                                 cfg: None,
336                             },
337                         ),
338                     },
339                     Annotation {
340                         range: 7..11,
341                         kind: HasImpls {
342                             position: FilePosition {
343                                 file_id: FileId(
344                                     0,
345                                 ),
346                                 offset: 7,
347                             },
348                             data: Some(
349                                 [],
350                             ),
351                         },
352                     },
353                     Annotation {
354                         range: 7..11,
355                         kind: HasReferences {
356                             position: FilePosition {
357                                 file_id: FileId(
358                                     0,
359                                 ),
360                                 offset: 7,
361                             },
362                             data: Some(
363                                 [
364                                     FileRange {
365                                         file_id: FileId(
366                                             0,
367                                         ),
368                                         range: 41..45,
369                                     },
370                                 ],
371                             ),
372                         },
373                     },
374                     Annotation {
375                         range: 17..21,
376                         kind: HasReferences {
377                             position: FilePosition {
378                                 file_id: FileId(
379                                     0,
380                                 ),
381                                 offset: 17,
382                             },
383                             data: Some(
384                                 [],
385                             ),
386                         },
387                     },
388                 ]
389             "#]],
390         );
391     }
392
393     #[test]
394     fn struct_and_trait_impls_annotations() {
395         check(
396             r#"
397 struct Test;
398
399 trait MyCoolTrait {}
400
401 impl MyCoolTrait for Test {}
402
403 fn main() {
404     let test = Test;
405 }
406             "#,
407             expect![[r#"
408                 [
409                     Annotation {
410                         range: 69..73,
411                         kind: Runnable(
412                             Runnable {
413                                 use_name_in_title: false,
414                                 nav: NavigationTarget {
415                                     file_id: FileId(
416                                         0,
417                                     ),
418                                     full_range: 66..100,
419                                     focus_range: 69..73,
420                                     name: "main",
421                                     kind: Function,
422                                 },
423                                 kind: Bin,
424                                 cfg: None,
425                             },
426                         ),
427                     },
428                     Annotation {
429                         range: 7..11,
430                         kind: HasImpls {
431                             position: FilePosition {
432                                 file_id: FileId(
433                                     0,
434                                 ),
435                                 offset: 7,
436                             },
437                             data: Some(
438                                 [
439                                     NavigationTarget {
440                                         file_id: FileId(
441                                             0,
442                                         ),
443                                         full_range: 36..64,
444                                         focus_range: 57..61,
445                                         name: "impl",
446                                         kind: Impl,
447                                     },
448                                 ],
449                             ),
450                         },
451                     },
452                     Annotation {
453                         range: 7..11,
454                         kind: HasReferences {
455                             position: FilePosition {
456                                 file_id: FileId(
457                                     0,
458                                 ),
459                                 offset: 7,
460                             },
461                             data: Some(
462                                 [
463                                     FileRange {
464                                         file_id: FileId(
465                                             0,
466                                         ),
467                                         range: 57..61,
468                                     },
469                                     FileRange {
470                                         file_id: FileId(
471                                             0,
472                                         ),
473                                         range: 93..97,
474                                     },
475                                 ],
476                             ),
477                         },
478                     },
479                     Annotation {
480                         range: 20..31,
481                         kind: HasImpls {
482                             position: FilePosition {
483                                 file_id: FileId(
484                                     0,
485                                 ),
486                                 offset: 20,
487                             },
488                             data: Some(
489                                 [
490                                     NavigationTarget {
491                                         file_id: FileId(
492                                             0,
493                                         ),
494                                         full_range: 36..64,
495                                         focus_range: 57..61,
496                                         name: "impl",
497                                         kind: Impl,
498                                     },
499                                 ],
500                             ),
501                         },
502                     },
503                     Annotation {
504                         range: 20..31,
505                         kind: HasReferences {
506                             position: FilePosition {
507                                 file_id: FileId(
508                                     0,
509                                 ),
510                                 offset: 20,
511                             },
512                             data: Some(
513                                 [
514                                     FileRange {
515                                         file_id: FileId(
516                                             0,
517                                         ),
518                                         range: 41..52,
519                                     },
520                                 ],
521                             ),
522                         },
523                     },
524                     Annotation {
525                         range: 69..73,
526                         kind: HasReferences {
527                             position: FilePosition {
528                                 file_id: FileId(
529                                     0,
530                                 ),
531                                 offset: 69,
532                             },
533                             data: Some(
534                                 [],
535                             ),
536                         },
537                     },
538                 ]
539             "#]],
540         );
541     }
542
543     #[test]
544     fn runnable_annotation() {
545         check(
546             r#"
547 fn main() {}
548             "#,
549             expect![[r#"
550                 [
551                     Annotation {
552                         range: 3..7,
553                         kind: Runnable(
554                             Runnable {
555                                 use_name_in_title: false,
556                                 nav: NavigationTarget {
557                                     file_id: FileId(
558                                         0,
559                                     ),
560                                     full_range: 0..12,
561                                     focus_range: 3..7,
562                                     name: "main",
563                                     kind: Function,
564                                 },
565                                 kind: Bin,
566                                 cfg: None,
567                             },
568                         ),
569                     },
570                     Annotation {
571                         range: 3..7,
572                         kind: HasReferences {
573                             position: FilePosition {
574                                 file_id: FileId(
575                                     0,
576                                 ),
577                                 offset: 3,
578                             },
579                             data: Some(
580                                 [],
581                             ),
582                         },
583                     },
584                 ]
585             "#]],
586         );
587     }
588
589     #[test]
590     fn method_annotations() {
591         check(
592             r#"
593 struct Test;
594
595 impl Test {
596     fn self_by_ref(&self) {}
597 }
598
599 fn main() {
600     Test.self_by_ref();
601 }
602             "#,
603             expect![[r#"
604                 [
605                     Annotation {
606                         range: 61..65,
607                         kind: Runnable(
608                             Runnable {
609                                 use_name_in_title: false,
610                                 nav: NavigationTarget {
611                                     file_id: FileId(
612                                         0,
613                                     ),
614                                     full_range: 58..95,
615                                     focus_range: 61..65,
616                                     name: "main",
617                                     kind: Function,
618                                 },
619                                 kind: Bin,
620                                 cfg: None,
621                             },
622                         ),
623                     },
624                     Annotation {
625                         range: 7..11,
626                         kind: HasImpls {
627                             position: FilePosition {
628                                 file_id: FileId(
629                                     0,
630                                 ),
631                                 offset: 7,
632                             },
633                             data: Some(
634                                 [
635                                     NavigationTarget {
636                                         file_id: FileId(
637                                             0,
638                                         ),
639                                         full_range: 14..56,
640                                         focus_range: 19..23,
641                                         name: "impl",
642                                         kind: Impl,
643                                     },
644                                 ],
645                             ),
646                         },
647                     },
648                     Annotation {
649                         range: 7..11,
650                         kind: HasReferences {
651                             position: FilePosition {
652                                 file_id: FileId(
653                                     0,
654                                 ),
655                                 offset: 7,
656                             },
657                             data: Some(
658                                 [
659                                     FileRange {
660                                         file_id: FileId(
661                                             0,
662                                         ),
663                                         range: 19..23,
664                                     },
665                                     FileRange {
666                                         file_id: FileId(
667                                             0,
668                                         ),
669                                         range: 74..78,
670                                     },
671                                 ],
672                             ),
673                         },
674                     },
675                     Annotation {
676                         range: 33..44,
677                         kind: HasReferences {
678                             position: FilePosition {
679                                 file_id: FileId(
680                                     0,
681                                 ),
682                                 offset: 33,
683                             },
684                             data: Some(
685                                 [
686                                     FileRange {
687                                         file_id: FileId(
688                                             0,
689                                         ),
690                                         range: 79..90,
691                                     },
692                                 ],
693                             ),
694                         },
695                     },
696                     Annotation {
697                         range: 61..65,
698                         kind: HasReferences {
699                             position: FilePosition {
700                                 file_id: FileId(
701                                     0,
702                                 ),
703                                 offset: 61,
704                             },
705                             data: Some(
706                                 [],
707                             ),
708                         },
709                     },
710                 ]
711             "#]],
712         );
713     }
714
715     #[test]
716     fn test_annotations() {
717         check(
718             r#"
719 fn main() {}
720
721 mod tests {
722     #[test]
723     fn my_cool_test() {}
724 }
725             "#,
726             expect![[r#"
727                 [
728                     Annotation {
729                         range: 3..7,
730                         kind: Runnable(
731                             Runnable {
732                                 use_name_in_title: false,
733                                 nav: NavigationTarget {
734                                     file_id: FileId(
735                                         0,
736                                     ),
737                                     full_range: 0..12,
738                                     focus_range: 3..7,
739                                     name: "main",
740                                     kind: Function,
741                                 },
742                                 kind: Bin,
743                                 cfg: None,
744                             },
745                         ),
746                     },
747                     Annotation {
748                         range: 18..23,
749                         kind: Runnable(
750                             Runnable {
751                                 use_name_in_title: false,
752                                 nav: NavigationTarget {
753                                     file_id: FileId(
754                                         0,
755                                     ),
756                                     full_range: 14..64,
757                                     focus_range: 18..23,
758                                     name: "tests",
759                                     kind: Module,
760                                     description: "mod tests",
761                                 },
762                                 kind: TestMod {
763                                     path: "tests",
764                                 },
765                                 cfg: None,
766                             },
767                         ),
768                     },
769                     Annotation {
770                         range: 45..57,
771                         kind: Runnable(
772                             Runnable {
773                                 use_name_in_title: false,
774                                 nav: NavigationTarget {
775                                     file_id: FileId(
776                                         0,
777                                     ),
778                                     full_range: 30..62,
779                                     focus_range: 45..57,
780                                     name: "my_cool_test",
781                                     kind: Function,
782                                 },
783                                 kind: Test {
784                                     test_id: Path(
785                                         "tests::my_cool_test",
786                                     ),
787                                     attr: TestAttr {
788                                         ignore: false,
789                                     },
790                                 },
791                                 cfg: None,
792                             },
793                         ),
794                     },
795                     Annotation {
796                         range: 3..7,
797                         kind: HasReferences {
798                             position: FilePosition {
799                                 file_id: FileId(
800                                     0,
801                                 ),
802                                 offset: 3,
803                             },
804                             data: Some(
805                                 [],
806                             ),
807                         },
808                     },
809                 ]
810             "#]],
811         );
812     }
813
814     #[test]
815     fn test_no_annotations_outside_module_tree() {
816         check(
817             r#"
818 //- /foo.rs
819 struct Foo;
820 //- /lib.rs
821 // this file comes last since `check` checks the first file only
822 "#,
823             expect![[r#"
824                 []
825             "#]],
826         );
827     }
828
829     #[test]
830     fn test_no_annotations_macro_struct_def() {
831         check(
832             r#"
833 //- /lib.rs
834 macro_rules! m {
835     () => {
836         struct A {}
837     };
838 }
839
840 m!();
841 "#,
842             expect![[r#"
843                 []
844             "#]],
845         );
846     }
847 }