diff options
Diffstat (limited to 'crates/ra_ide/src/goto_definition.rs')
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs new file mode 100644 index 000000000..c10a6c844 --- /dev/null +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -0,0 +1,696 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{db::AstDatabase, Source}; | ||
4 | use ra_syntax::{ | ||
5 | ast::{self, DocCommentsOwner}, | ||
6 | match_ast, AstNode, SyntaxNode, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | db::RootDatabase, | ||
11 | display::{ShortLabel, ToNav}, | ||
12 | expand::descend_into_macros, | ||
13 | references::{classify_name_ref, NameKind::*}, | ||
14 | FilePosition, NavigationTarget, RangeInfo, | ||
15 | }; | ||
16 | |||
17 | pub(crate) fn goto_definition( | ||
18 | db: &RootDatabase, | ||
19 | position: FilePosition, | ||
20 | ) -> Option<RangeInfo<Vec<NavigationTarget>>> { | ||
21 | let file = db.parse_or_expand(position.file_id.into())?; | ||
22 | let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; | ||
23 | let token = descend_into_macros(db, position.file_id, token); | ||
24 | |||
25 | let res = match_ast! { | ||
26 | match (token.value.parent()) { | ||
27 | ast::NameRef(name_ref) => { | ||
28 | let navs = reference_definition(db, token.with_value(&name_ref)).to_vec(); | ||
29 | RangeInfo::new(name_ref.syntax().text_range(), navs.to_vec()) | ||
30 | }, | ||
31 | ast::Name(name) => { | ||
32 | let navs = name_definition(db, token.with_value(&name))?; | ||
33 | RangeInfo::new(name.syntax().text_range(), navs) | ||
34 | |||
35 | }, | ||
36 | _ => return None, | ||
37 | } | ||
38 | }; | ||
39 | |||
40 | Some(res) | ||
41 | } | ||
42 | |||
43 | #[derive(Debug)] | ||
44 | pub(crate) enum ReferenceResult { | ||
45 | Exact(NavigationTarget), | ||
46 | Approximate(Vec<NavigationTarget>), | ||
47 | } | ||
48 | |||
49 | impl ReferenceResult { | ||
50 | fn to_vec(self) -> Vec<NavigationTarget> { | ||
51 | use self::ReferenceResult::*; | ||
52 | match self { | ||
53 | Exact(target) => vec![target], | ||
54 | Approximate(vec) => vec, | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | pub(crate) fn reference_definition( | ||
60 | db: &RootDatabase, | ||
61 | name_ref: Source<&ast::NameRef>, | ||
62 | ) -> ReferenceResult { | ||
63 | use self::ReferenceResult::*; | ||
64 | |||
65 | let name_kind = classify_name_ref(db, name_ref).map(|d| d.kind); | ||
66 | match name_kind { | ||
67 | Some(Macro(mac)) => return Exact(mac.to_nav(db)), | ||
68 | Some(Field(field)) => return Exact(field.to_nav(db)), | ||
69 | Some(AssocItem(assoc)) => return Exact(assoc.to_nav(db)), | ||
70 | Some(Def(def)) => match NavigationTarget::from_def(db, def) { | ||
71 | Some(nav) => return Exact(nav), | ||
72 | None => return Approximate(vec![]), | ||
73 | }, | ||
74 | Some(SelfType(imp)) => { | ||
75 | // FIXME: ideally, this should point to the type in the impl, and | ||
76 | // not at the whole impl. And goto **type** definition should bring | ||
77 | // us to the actual type | ||
78 | return Exact(imp.to_nav(db)); | ||
79 | } | ||
80 | Some(Local(local)) => return Exact(local.to_nav(db)), | ||
81 | Some(GenericParam(_)) => { | ||
82 | // FIXME: go to the generic param def | ||
83 | } | ||
84 | None => {} | ||
85 | }; | ||
86 | |||
87 | // Fallback index based approach: | ||
88 | let navs = crate::symbol_index::index_resolve(db, name_ref.value) | ||
89 | .into_iter() | ||
90 | .map(|s| s.to_nav(db)) | ||
91 | .collect(); | ||
92 | Approximate(navs) | ||
93 | } | ||
94 | |||
95 | pub(crate) fn name_definition( | ||
96 | db: &RootDatabase, | ||
97 | name: Source<&ast::Name>, | ||
98 | ) -> Option<Vec<NavigationTarget>> { | ||
99 | let parent = name.value.syntax().parent()?; | ||
100 | |||
101 | if let Some(module) = ast::Module::cast(parent.clone()) { | ||
102 | if module.has_semi() { | ||
103 | let src = name.with_value(module); | ||
104 | if let Some(child_module) = hir::Module::from_declaration(db, src) { | ||
105 | let nav = child_module.to_nav(db); | ||
106 | return Some(vec![nav]); | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | if let Some(nav) = named_target(db, name.with_value(&parent)) { | ||
112 | return Some(vec![nav]); | ||
113 | } | ||
114 | |||
115 | None | ||
116 | } | ||
117 | |||
118 | fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<NavigationTarget> { | ||
119 | match_ast! { | ||
120 | match (node.value) { | ||
121 | ast::StructDef(it) => { | ||
122 | Some(NavigationTarget::from_named( | ||
123 | db, | ||
124 | node.with_value(&it), | ||
125 | it.doc_comment_text(), | ||
126 | it.short_label(), | ||
127 | )) | ||
128 | }, | ||
129 | ast::EnumDef(it) => { | ||
130 | Some(NavigationTarget::from_named( | ||
131 | db, | ||
132 | node.with_value(&it), | ||
133 | it.doc_comment_text(), | ||
134 | it.short_label(), | ||
135 | )) | ||
136 | }, | ||
137 | ast::EnumVariant(it) => { | ||
138 | Some(NavigationTarget::from_named( | ||
139 | db, | ||
140 | node.with_value(&it), | ||
141 | it.doc_comment_text(), | ||
142 | it.short_label(), | ||
143 | )) | ||
144 | }, | ||
145 | ast::FnDef(it) => { | ||
146 | Some(NavigationTarget::from_named( | ||
147 | db, | ||
148 | node.with_value(&it), | ||
149 | it.doc_comment_text(), | ||
150 | it.short_label(), | ||
151 | )) | ||
152 | }, | ||
153 | ast::TypeAliasDef(it) => { | ||
154 | Some(NavigationTarget::from_named( | ||
155 | db, | ||
156 | node.with_value(&it), | ||
157 | it.doc_comment_text(), | ||
158 | it.short_label(), | ||
159 | )) | ||
160 | }, | ||
161 | ast::ConstDef(it) => { | ||
162 | Some(NavigationTarget::from_named( | ||
163 | db, | ||
164 | node.with_value(&it), | ||
165 | it.doc_comment_text(), | ||
166 | it.short_label(), | ||
167 | )) | ||
168 | }, | ||
169 | ast::StaticDef(it) => { | ||
170 | Some(NavigationTarget::from_named( | ||
171 | db, | ||
172 | node.with_value(&it), | ||
173 | it.doc_comment_text(), | ||
174 | it.short_label(), | ||
175 | )) | ||
176 | }, | ||
177 | ast::TraitDef(it) => { | ||
178 | Some(NavigationTarget::from_named( | ||
179 | db, | ||
180 | node.with_value(&it), | ||
181 | it.doc_comment_text(), | ||
182 | it.short_label(), | ||
183 | )) | ||
184 | }, | ||
185 | ast::RecordFieldDef(it) => { | ||
186 | Some(NavigationTarget::from_named( | ||
187 | db, | ||
188 | node.with_value(&it), | ||
189 | it.doc_comment_text(), | ||
190 | it.short_label(), | ||
191 | )) | ||
192 | }, | ||
193 | ast::Module(it) => { | ||
194 | Some(NavigationTarget::from_named( | ||
195 | db, | ||
196 | node.with_value(&it), | ||
197 | it.doc_comment_text(), | ||
198 | it.short_label(), | ||
199 | )) | ||
200 | }, | ||
201 | ast::MacroCall(it) => { | ||
202 | Some(NavigationTarget::from_named( | ||
203 | db, | ||
204 | node.with_value(&it), | ||
205 | it.doc_comment_text(), | ||
206 | None, | ||
207 | )) | ||
208 | }, | ||
209 | _ => None, | ||
210 | } | ||
211 | } | ||
212 | } | ||
213 | |||
214 | #[cfg(test)] | ||
215 | mod tests { | ||
216 | use test_utils::covers; | ||
217 | |||
218 | use crate::mock_analysis::analysis_and_position; | ||
219 | |||
220 | fn check_goto(fixture: &str, expected: &str) { | ||
221 | let (analysis, pos) = analysis_and_position(fixture); | ||
222 | |||
223 | let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; | ||
224 | assert_eq!(navs.len(), 1); | ||
225 | let nav = navs.pop().unwrap(); | ||
226 | nav.assert_match(expected); | ||
227 | } | ||
228 | |||
229 | #[test] | ||
230 | fn goto_definition_works_in_items() { | ||
231 | check_goto( | ||
232 | " | ||
233 | //- /lib.rs | ||
234 | struct Foo; | ||
235 | enum E { X(Foo<|>) } | ||
236 | ", | ||
237 | "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn goto_definition_resolves_correct_name() { | ||
243 | check_goto( | ||
244 | " | ||
245 | //- /lib.rs | ||
246 | use a::Foo; | ||
247 | mod a; | ||
248 | mod b; | ||
249 | enum E { X(Foo<|>) } | ||
250 | //- /a.rs | ||
251 | struct Foo; | ||
252 | //- /b.rs | ||
253 | struct Foo; | ||
254 | ", | ||
255 | "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)", | ||
256 | ); | ||
257 | } | ||
258 | |||
259 | #[test] | ||
260 | fn goto_definition_works_for_module_declaration() { | ||
261 | check_goto( | ||
262 | " | ||
263 | //- /lib.rs | ||
264 | mod <|>foo; | ||
265 | //- /foo.rs | ||
266 | // empty | ||
267 | ", | ||
268 | "foo SOURCE_FILE FileId(2) [0; 10)", | ||
269 | ); | ||
270 | |||
271 | check_goto( | ||
272 | " | ||
273 | //- /lib.rs | ||
274 | mod <|>foo; | ||
275 | //- /foo/mod.rs | ||
276 | // empty | ||
277 | ", | ||
278 | "foo SOURCE_FILE FileId(2) [0; 10)", | ||
279 | ); | ||
280 | } | ||
281 | |||
282 | #[test] | ||
283 | fn goto_definition_works_for_macros() { | ||
284 | covers!(goto_definition_works_for_macros); | ||
285 | check_goto( | ||
286 | " | ||
287 | //- /lib.rs | ||
288 | macro_rules! foo { | ||
289 | () => { | ||
290 | {} | ||
291 | }; | ||
292 | } | ||
293 | |||
294 | fn bar() { | ||
295 | <|>foo!(); | ||
296 | } | ||
297 | ", | ||
298 | "foo MACRO_CALL FileId(1) [0; 50) [13; 16)", | ||
299 | ); | ||
300 | } | ||
301 | |||
302 | #[test] | ||
303 | fn goto_definition_works_for_macros_from_other_crates() { | ||
304 | covers!(goto_definition_works_for_macros); | ||
305 | check_goto( | ||
306 | " | ||
307 | //- /lib.rs | ||
308 | use foo::foo; | ||
309 | fn bar() { | ||
310 | <|>foo!(); | ||
311 | } | ||
312 | |||
313 | //- /foo/lib.rs | ||
314 | #[macro_export] | ||
315 | macro_rules! foo { | ||
316 | () => { | ||
317 | {} | ||
318 | }; | ||
319 | } | ||
320 | ", | ||
321 | "foo MACRO_CALL FileId(2) [0; 66) [29; 32)", | ||
322 | ); | ||
323 | } | ||
324 | |||
325 | #[test] | ||
326 | fn goto_definition_works_for_macros_in_use_tree() { | ||
327 | check_goto( | ||
328 | " | ||
329 | //- /lib.rs | ||
330 | use foo::foo<|>; | ||
331 | |||
332 | //- /foo/lib.rs | ||
333 | #[macro_export] | ||
334 | macro_rules! foo { | ||
335 | () => { | ||
336 | {} | ||
337 | }; | ||
338 | } | ||
339 | ", | ||
340 | "foo MACRO_CALL FileId(2) [0; 66) [29; 32)", | ||
341 | ); | ||
342 | } | ||
343 | |||
344 | #[test] | ||
345 | fn goto_definition_works_for_macro_defined_fn_with_arg() { | ||
346 | check_goto( | ||
347 | " | ||
348 | //- /lib.rs | ||
349 | macro_rules! define_fn { | ||
350 | ($name:ident) => (fn $name() {}) | ||
351 | } | ||
352 | |||
353 | define_fn!( | ||
354 | foo | ||
355 | ) | ||
356 | |||
357 | fn bar() { | ||
358 | <|>foo(); | ||
359 | } | ||
360 | ", | ||
361 | "foo FN_DEF FileId(1) [80; 83) [80; 83)", | ||
362 | ); | ||
363 | } | ||
364 | |||
365 | #[test] | ||
366 | fn goto_definition_works_for_macro_defined_fn_no_arg() { | ||
367 | check_goto( | ||
368 | " | ||
369 | //- /lib.rs | ||
370 | macro_rules! define_fn { | ||
371 | () => (fn foo() {}) | ||
372 | } | ||
373 | |||
374 | define_fn!(); | ||
375 | |||
376 | fn bar() { | ||
377 | <|>foo(); | ||
378 | } | ||
379 | ", | ||
380 | "foo FN_DEF FileId(1) [39; 42) [39; 42)", | ||
381 | ); | ||
382 | } | ||
383 | |||
384 | #[test] | ||
385 | fn goto_definition_works_for_methods() { | ||
386 | covers!(goto_definition_works_for_methods); | ||
387 | check_goto( | ||
388 | " | ||
389 | //- /lib.rs | ||
390 | struct Foo; | ||
391 | impl Foo { | ||
392 | fn frobnicate(&self) { } | ||
393 | } | ||
394 | |||
395 | fn bar(foo: &Foo) { | ||
396 | foo.frobnicate<|>(); | ||
397 | } | ||
398 | ", | ||
399 | "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)", | ||
400 | ); | ||
401 | } | ||
402 | |||
403 | #[test] | ||
404 | fn goto_definition_works_for_fields() { | ||
405 | covers!(goto_definition_works_for_fields); | ||
406 | check_goto( | ||
407 | " | ||
408 | //- /lib.rs | ||
409 | struct Foo { | ||
410 | spam: u32, | ||
411 | } | ||
412 | |||
413 | fn bar(foo: &Foo) { | ||
414 | foo.spam<|>; | ||
415 | } | ||
416 | ", | ||
417 | "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)", | ||
418 | ); | ||
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn goto_definition_works_for_record_fields() { | ||
423 | covers!(goto_definition_works_for_record_fields); | ||
424 | check_goto( | ||
425 | " | ||
426 | //- /lib.rs | ||
427 | struct Foo { | ||
428 | spam: u32, | ||
429 | } | ||
430 | |||
431 | fn bar() -> Foo { | ||
432 | Foo { | ||
433 | spam<|>: 0, | ||
434 | } | ||
435 | } | ||
436 | ", | ||
437 | "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)", | ||
438 | ); | ||
439 | } | ||
440 | |||
441 | #[test] | ||
442 | fn goto_definition_works_for_ufcs_inherent_methods() { | ||
443 | check_goto( | ||
444 | " | ||
445 | //- /lib.rs | ||
446 | struct Foo; | ||
447 | impl Foo { | ||
448 | fn frobnicate() { } | ||
449 | } | ||
450 | |||
451 | fn bar(foo: &Foo) { | ||
452 | Foo::frobnicate<|>(); | ||
453 | } | ||
454 | ", | ||
455 | "frobnicate FN_DEF FileId(1) [27; 47) [30; 40)", | ||
456 | ); | ||
457 | } | ||
458 | |||
459 | #[test] | ||
460 | fn goto_definition_works_for_ufcs_trait_methods_through_traits() { | ||
461 | check_goto( | ||
462 | " | ||
463 | //- /lib.rs | ||
464 | trait Foo { | ||
465 | fn frobnicate(); | ||
466 | } | ||
467 | |||
468 | fn bar() { | ||
469 | Foo::frobnicate<|>(); | ||
470 | } | ||
471 | ", | ||
472 | "frobnicate FN_DEF FileId(1) [16; 32) [19; 29)", | ||
473 | ); | ||
474 | } | ||
475 | |||
476 | #[test] | ||
477 | fn goto_definition_works_for_ufcs_trait_methods_through_self() { | ||
478 | check_goto( | ||
479 | " | ||
480 | //- /lib.rs | ||
481 | struct Foo; | ||
482 | trait Trait { | ||
483 | fn frobnicate(); | ||
484 | } | ||
485 | impl Trait for Foo {} | ||
486 | |||
487 | fn bar() { | ||
488 | Foo::frobnicate<|>(); | ||
489 | } | ||
490 | ", | ||
491 | "frobnicate FN_DEF FileId(1) [30; 46) [33; 43)", | ||
492 | ); | ||
493 | } | ||
494 | |||
495 | #[test] | ||
496 | fn goto_definition_on_self() { | ||
497 | check_goto( | ||
498 | " | ||
499 | //- /lib.rs | ||
500 | struct Foo; | ||
501 | impl Foo { | ||
502 | pub fn new() -> Self { | ||
503 | Self<|> {} | ||
504 | } | ||
505 | } | ||
506 | ", | ||
507 | "impl IMPL_BLOCK FileId(1) [12; 73)", | ||
508 | ); | ||
509 | |||
510 | check_goto( | ||
511 | " | ||
512 | //- /lib.rs | ||
513 | struct Foo; | ||
514 | impl Foo { | ||
515 | pub fn new() -> Self<|> { | ||
516 | Self {} | ||
517 | } | ||
518 | } | ||
519 | ", | ||
520 | "impl IMPL_BLOCK FileId(1) [12; 73)", | ||
521 | ); | ||
522 | |||
523 | check_goto( | ||
524 | " | ||
525 | //- /lib.rs | ||
526 | enum Foo { A } | ||
527 | impl Foo { | ||
528 | pub fn new() -> Self<|> { | ||
529 | Foo::A | ||
530 | } | ||
531 | } | ||
532 | ", | ||
533 | "impl IMPL_BLOCK FileId(1) [15; 75)", | ||
534 | ); | ||
535 | |||
536 | check_goto( | ||
537 | " | ||
538 | //- /lib.rs | ||
539 | enum Foo { A } | ||
540 | impl Foo { | ||
541 | pub fn thing(a: &Self<|>) { | ||
542 | } | ||
543 | } | ||
544 | ", | ||
545 | "impl IMPL_BLOCK FileId(1) [15; 62)", | ||
546 | ); | ||
547 | } | ||
548 | |||
549 | #[test] | ||
550 | fn goto_definition_on_self_in_trait_impl() { | ||
551 | check_goto( | ||
552 | " | ||
553 | //- /lib.rs | ||
554 | struct Foo; | ||
555 | trait Make { | ||
556 | fn new() -> Self; | ||
557 | } | ||
558 | impl Make for Foo { | ||
559 | fn new() -> Self { | ||
560 | Self<|> {} | ||
561 | } | ||
562 | } | ||
563 | ", | ||
564 | "impl IMPL_BLOCK FileId(1) [49; 115)", | ||
565 | ); | ||
566 | |||
567 | check_goto( | ||
568 | " | ||
569 | //- /lib.rs | ||
570 | struct Foo; | ||
571 | trait Make { | ||
572 | fn new() -> Self; | ||
573 | } | ||
574 | impl Make for Foo { | ||
575 | fn new() -> Self<|> { | ||
576 | Self {} | ||
577 | } | ||
578 | } | ||
579 | ", | ||
580 | "impl IMPL_BLOCK FileId(1) [49; 115)", | ||
581 | ); | ||
582 | } | ||
583 | |||
584 | #[test] | ||
585 | fn goto_definition_works_when_used_on_definition_name_itself() { | ||
586 | check_goto( | ||
587 | " | ||
588 | //- /lib.rs | ||
589 | struct Foo<|> { value: u32 } | ||
590 | ", | ||
591 | "Foo STRUCT_DEF FileId(1) [0; 25) [7; 10)", | ||
592 | ); | ||
593 | |||
594 | check_goto( | ||
595 | r#" | ||
596 | //- /lib.rs | ||
597 | struct Foo { | ||
598 | field<|>: string, | ||
599 | } | ||
600 | "#, | ||
601 | "field RECORD_FIELD_DEF FileId(1) [17; 30) [17; 22)", | ||
602 | ); | ||
603 | |||
604 | check_goto( | ||
605 | " | ||
606 | //- /lib.rs | ||
607 | fn foo_test<|>() { | ||
608 | } | ||
609 | ", | ||
610 | "foo_test FN_DEF FileId(1) [0; 17) [3; 11)", | ||
611 | ); | ||
612 | |||
613 | check_goto( | ||
614 | " | ||
615 | //- /lib.rs | ||
616 | enum Foo<|> { | ||
617 | Variant, | ||
618 | } | ||
619 | ", | ||
620 | "Foo ENUM_DEF FileId(1) [0; 25) [5; 8)", | ||
621 | ); | ||
622 | |||
623 | check_goto( | ||
624 | " | ||
625 | //- /lib.rs | ||
626 | enum Foo { | ||
627 | Variant1, | ||
628 | Variant2<|>, | ||
629 | Variant3, | ||
630 | } | ||
631 | ", | ||
632 | "Variant2 ENUM_VARIANT FileId(1) [29; 37) [29; 37)", | ||
633 | ); | ||
634 | |||
635 | check_goto( | ||
636 | r#" | ||
637 | //- /lib.rs | ||
638 | static inner<|>: &str = ""; | ||
639 | "#, | ||
640 | "inner STATIC_DEF FileId(1) [0; 24) [7; 12)", | ||
641 | ); | ||
642 | |||
643 | check_goto( | ||
644 | r#" | ||
645 | //- /lib.rs | ||
646 | const inner<|>: &str = ""; | ||
647 | "#, | ||
648 | "inner CONST_DEF FileId(1) [0; 23) [6; 11)", | ||
649 | ); | ||
650 | |||
651 | check_goto( | ||
652 | r#" | ||
653 | //- /lib.rs | ||
654 | type Thing<|> = Option<()>; | ||
655 | "#, | ||
656 | "Thing TYPE_ALIAS_DEF FileId(1) [0; 24) [5; 10)", | ||
657 | ); | ||
658 | |||
659 | check_goto( | ||
660 | r#" | ||
661 | //- /lib.rs | ||
662 | trait Foo<|> { | ||
663 | } | ||
664 | "#, | ||
665 | "Foo TRAIT_DEF FileId(1) [0; 13) [6; 9)", | ||
666 | ); | ||
667 | |||
668 | check_goto( | ||
669 | r#" | ||
670 | //- /lib.rs | ||
671 | mod bar<|> { | ||
672 | } | ||
673 | "#, | ||
674 | "bar MODULE FileId(1) [0; 11) [4; 7)", | ||
675 | ); | ||
676 | } | ||
677 | |||
678 | #[test] | ||
679 | fn goto_from_macro() { | ||
680 | check_goto( | ||
681 | " | ||
682 | //- /lib.rs | ||
683 | macro_rules! id { | ||
684 | ($($tt:tt)*) => { $($tt)* } | ||
685 | } | ||
686 | fn foo() {} | ||
687 | id! { | ||
688 | fn bar() { | ||
689 | fo<|>o(); | ||
690 | } | ||
691 | } | ||
692 | ", | ||
693 | "foo FN_DEF FileId(1) [52; 63) [55; 58)", | ||
694 | ); | ||
695 | } | ||
696 | } | ||