aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/annotations.rs267
1 files changed, 267 insertions, 0 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index a6f093ad7..97a361194 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -18,11 +18,13 @@ use crate::{
18// 18//
19// Provides user with annotations above items for looking up references or impl blocks 19// Provides user with annotations above items for looking up references or impl blocks
20// and running/debugging binaries. 20// and running/debugging binaries.
21#[derive(Debug)]
21pub struct Annotation { 22pub struct Annotation {
22 pub range: TextRange, 23 pub range: TextRange,
23 pub kind: AnnotationKind, 24 pub kind: AnnotationKind,
24} 25}
25 26
27#[derive(Debug)]
26pub enum AnnotationKind { 28pub enum AnnotationKind {
27 Runnable { debug: bool, runnable: Runnable }, 29 Runnable { debug: bool, runnable: Runnable },
28 HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> }, 30 HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> },
@@ -141,3 +143,268 @@ pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation)
141 143
142 annotation 144 annotation
143} 145}
146
147#[cfg(test)]
148mod tests {
149 use ide_db::base_db::{FileId, FileRange};
150 use syntax::{TextRange, TextSize};
151
152 use crate::{fixture, Annotation, AnnotationConfig, AnnotationKind, RunnableKind};
153
154 fn get_annotations(
155 ra_fixture: &str,
156 annotation_config: AnnotationConfig,
157 ) -> (FileId, Vec<Annotation>) {
158 let (analysis, file_id) = fixture::file(ra_fixture);
159
160 let annotations: Vec<Annotation> = analysis
161 .annotations(file_id, annotation_config)
162 .unwrap()
163 .into_iter()
164 .map(move |annotation| analysis.resolve_annotation(annotation).unwrap())
165 .collect();
166
167 if annotations.len() == 0 {
168 panic!("unresolved annotations")
169 }
170
171 (file_id, annotations)
172 }
173
174 macro_rules! check_annotation {
175 ( $ra_fixture:expr, $config:expr, $item_positions:expr, $pattern:pat, $checker:expr ) => {
176 let (file_id, annotations) = get_annotations($ra_fixture, $config);
177
178 annotations.into_iter().for_each(|annotation| {
179 assert!($item_positions.contains(&annotation.range));
180
181 match annotation.kind {
182 $pattern => $checker(file_id),
183 _ => panic!("Unexpected annotation kind"),
184 }
185 });
186 };
187 }
188
189 #[test]
190 fn const_annotations() {
191 check_annotation!(
192 r#"
193const DEMO: i32 = 123;
194
195fn main() {
196 let hello = DEMO;
197}
198 "#,
199 AnnotationConfig {
200 binary_target: false,
201 annotate_runnables: false,
202 annotate_impls: false,
203 annotate_references: true,
204 annotate_method_references: false,
205 run: false,
206 debug: false,
207 },
208 &[TextRange::new(TextSize::from(0), TextSize::from(22))],
209 AnnotationKind::HasReferences { data: Some(ranges), .. },
210 |file_id| assert_eq!(
211 *ranges.first().unwrap(),
212 FileRange {
213 file_id,
214 range: TextRange::new(TextSize::from(52), TextSize::from(56))
215 }
216 )
217 );
218 }
219
220 #[test]
221 fn unused_const_annotations() {
222 check_annotation!(
223 r#"
224const DEMO: i32 = 123;
225
226fn main() {}
227 "#,
228 AnnotationConfig {
229 binary_target: false,
230 annotate_runnables: false,
231 annotate_impls: false,
232 annotate_references: true,
233 annotate_method_references: false,
234 run: false,
235 debug: false,
236 },
237 &[TextRange::new(TextSize::from(0), TextSize::from(22))],
238 AnnotationKind::HasReferences { data: Some(ranges), .. },
239 |_| assert_eq!(ranges.len(), 0)
240 );
241 }
242
243 #[test]
244 fn struct_references_annotations() {
245 check_annotation!(
246 r#"
247struct Test;
248
249fn main() {
250 let test = Test;
251}
252 "#,
253 AnnotationConfig {
254 binary_target: false,
255 annotate_runnables: false,
256 annotate_impls: false,
257 annotate_references: true,
258 annotate_method_references: false,
259 run: false,
260 debug: false,
261 },
262 &[TextRange::new(TextSize::from(0), TextSize::from(12))],
263 AnnotationKind::HasReferences { data: Some(ranges), .. },
264 |file_id| assert_eq!(
265 *ranges.first().unwrap(),
266 FileRange {
267 file_id,
268 range: TextRange::new(TextSize::from(41), TextSize::from(45))
269 }
270 )
271 );
272 }
273
274 #[test]
275 fn struct_and_trait_impls_annotations() {
276 check_annotation!(
277 r#"
278struct Test;
279
280trait MyCoolTrait {}
281
282impl MyCoolTrait for Test {}
283
284fn main() {
285 let test = Test;
286}
287 "#,
288 AnnotationConfig {
289 binary_target: false,
290 annotate_runnables: false,
291 annotate_impls: true,
292 annotate_references: false,
293 annotate_method_references: false,
294 run: false,
295 debug: false,
296 },
297 &[
298 TextRange::new(TextSize::from(0), TextSize::from(12)),
299 TextRange::new(TextSize::from(14), TextSize::from(34))
300 ],
301 AnnotationKind::HasImpls { data: Some(ranges), .. },
302 |_| assert_eq!(
303 ranges.first().unwrap().full_range,
304 TextRange::new(TextSize::from(36), TextSize::from(64))
305 )
306 );
307 }
308
309 #[test]
310 fn run_annotation() {
311 check_annotation!(
312 r#"
313fn main() {}
314 "#,
315 AnnotationConfig {
316 binary_target: true,
317 annotate_runnables: true,
318 annotate_impls: false,
319 annotate_references: false,
320 annotate_method_references: false,
321 run: true,
322 debug: false,
323 },
324 &[TextRange::new(TextSize::from(0), TextSize::from(12))],
325 AnnotationKind::Runnable { debug: false, runnable },
326 |_| {
327 assert!(matches!(runnable.kind, RunnableKind::Bin));
328 assert!(runnable.action().run_title.contains("Run"));
329 }
330 );
331 }
332
333 #[test]
334 fn debug_annotation() {
335 check_annotation!(
336 r#"
337fn main() {}
338 "#,
339 AnnotationConfig {
340 binary_target: true,
341 annotate_runnables: true,
342 annotate_impls: false,
343 annotate_references: false,
344 annotate_method_references: false,
345 run: false,
346 debug: true,
347 },
348 &[TextRange::new(TextSize::from(0), TextSize::from(12))],
349 AnnotationKind::Runnable { debug: true, runnable },
350 |_| {
351 assert!(matches!(runnable.kind, RunnableKind::Bin));
352 assert!(runnable.action().debugee);
353 }
354 );
355 }
356
357 #[test]
358 fn method_annotations() {
359 // We actually want to skip `fn main` annotation, as it has no references in it
360 // but just ignoring empty reference slices would lead to false-positive if something
361 // goes wrong in annotation resolving mechanism. By tracking if we iterated before finding
362 // an empty slice we can track if everything is settled.
363 let mut iterated_once = false;
364
365 check_annotation!(
366 r#"
367struct Test;
368
369impl Test {
370 fn self_by_ref(&self) {}
371}
372
373fn main() {
374 Test.self_by_ref();
375}
376 "#,
377 AnnotationConfig {
378 binary_target: false,
379 annotate_runnables: false,
380 annotate_impls: false,
381 annotate_references: false,
382 annotate_method_references: true,
383 run: false,
384 debug: false,
385 },
386 &[
387 TextRange::new(TextSize::from(33), TextSize::from(44)),
388 TextRange::new(TextSize::from(61), TextSize::from(65))
389 ],
390 AnnotationKind::HasReferences { data: Some(ranges), .. },
391 |file_id| {
392 match ranges.as_slice() {
393 [first, ..] => {
394 assert_eq!(
395 *first,
396 FileRange {
397 file_id,
398 range: TextRange::new(TextSize::from(79), TextSize::from(90))
399 }
400 );
401
402 iterated_once = true;
403 }
404 [] if iterated_once => {}
405 [] => panic!("One reference was expected but not found"),
406 }
407 }
408 );
409 }
410}