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