diff options
-rw-r--r-- | crates/ide/src/annotations.rs | 267 |
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)] | ||
21 | pub struct Annotation { | 22 | pub 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)] | ||
26 | pub enum AnnotationKind { | 28 | pub 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)] | ||
148 | mod 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#" | ||
193 | const DEMO: i32 = 123; | ||
194 | |||
195 | fn 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#" | ||
224 | const DEMO: i32 = 123; | ||
225 | |||
226 | fn 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#" | ||
247 | struct Test; | ||
248 | |||
249 | fn 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#" | ||
278 | struct Test; | ||
279 | |||
280 | trait MyCoolTrait {} | ||
281 | |||
282 | impl MyCoolTrait for Test {} | ||
283 | |||
284 | fn 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#" | ||
313 | fn 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#" | ||
337 | fn 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#" | ||
367 | struct Test; | ||
368 | |||
369 | impl Test { | ||
370 | fn self_by_ref(&self) {} | ||
371 | } | ||
372 | |||
373 | fn 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 | } | ||