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