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