1use std::{
2 fmt::{self, Display, Formatter},
3 str::FromStr,
4 sync::OnceLock,
5};
6
7use regex::{Captures, Regex};
8
9use super::format::find_method_or_function;
10use crate::{
11 analysis::object::LocationInObject,
12 codegen::doc::format::{
13 gen_alias_doc_link, gen_callback_doc_link, gen_const_doc_link, gen_object_fn_doc_link,
14 gen_property_doc_link, gen_signal_doc_link, gen_symbol_doc_link, gen_vfunc_doc_link,
15 },
16 library::{TypeId, MAIN_NAMESPACE},
17 nameutil::mangle_keywords,
18 Env,
19};
20
21#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
22pub enum GiDocgenError {
23 InvalidLinkType(String),
24 BrokenLinkType(String),
25 InvalidLink,
26}
27
28impl Display for GiDocgenError {
29 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::InvalidLinkType(e) => f.write_str(&format!("Invalid link type \"{e}\"")),
32 Self::BrokenLinkType(e) => f.write_str(&format!("Broken link syntax for type \"{e}\"")),
33 Self::InvalidLink => f.write_str("Invalid link syntax"),
34 }
35 }
36}
37
38impl std::error::Error for GiDocgenError {}
39
40fn namespace_type_from_details(
42 link_details: &str,
43 link_type: &str,
44) -> Result<(Option<String>, String), GiDocgenError> {
45 let res: Vec<&str> = link_details.split('.').collect();
46 let len = res.len();
47 if len == 1 {
48 Ok((None, res[0].to_string()))
49 } else if len == 2 {
50 if res[1].is_empty() {
51 Err(GiDocgenError::BrokenLinkType(link_type.to_string()))
52 } else {
53 Ok((Some(res[0].to_string()), res[1].to_string()))
54 }
55 } else {
56 Err(GiDocgenError::BrokenLinkType(link_type.to_string()))
57 }
58}
59
60fn namespace_type_method_from_details(
64 link_details: &str,
65 link_type: &str,
66 is_global_func: bool,
67) -> Result<(Option<String>, Option<String>, String), GiDocgenError> {
68 let res: Vec<&str> = link_details.split('.').collect();
69 let len = res.len();
70 if len == 1 {
71 Ok((None, None, res[0].to_string()))
72 } else if len == 2 {
73 if res[1].is_empty() {
74 Err(GiDocgenError::BrokenLinkType(link_type.to_string()))
75 } else if is_global_func {
76 Ok((Some(res[0].to_string()), None, res[1].to_string()))
77 } else {
78 Ok((None, Some(res[0].to_string()), res[1].to_string()))
79 }
80 } else if len == 3 {
81 if res[2].is_empty() {
82 Err(GiDocgenError::BrokenLinkType(link_type.to_string()))
83 } else {
84 Ok((
85 Some(res[0].to_string()),
86 Some(res[1].to_string()),
87 res[2].to_string(),
88 ))
89 }
90 } else {
91 Err(GiDocgenError::BrokenLinkType(link_type.to_string()))
92 }
93}
94
95fn gi_docgen_symbols() -> &'static Regex {
96 static REGEX: OnceLock<Regex> = OnceLock::new();
97 REGEX.get_or_init(|| {
98 Regex::new(r"\[(callback|id|alias|class|const|ctor|enum|error|flags|func|iface|method|property|signal|struct|vfunc)[@](\w+\b)([:.]+[\w-]+\b)?([:.]+[\w-]+\b)?\]?").unwrap()
99 })
100}
101
102pub(crate) fn replace_c_types(
103 entry: &str,
104 env: &Env,
105 in_type: Option<(&TypeId, Option<LocationInObject>)>,
106) -> String {
107 gi_docgen_symbols()
108 .replace_all(entry, |caps: &Captures<'_>| {
109 if let Ok(gi_type) = GiDocgen::from_str(&caps[0]) {
110 gi_type.rust_link(env, in_type)
111 } else {
112 caps[0].to_string()
114 }
115 })
116 .to_string()
117}
118
119#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
123pub enum GiDocgen {
124 Id(String),
126 Alias(String),
128 Class {
130 namespace: Option<String>,
131 type_: String,
132 },
133 Const {
134 namespace: Option<String>,
135 type_: String,
136 },
137 Constructor {
138 namespace: Option<String>,
139 type_: String,
140 name: String,
141 },
142 Callback {
143 namespace: Option<String>,
144 name: String,
145 },
146 Enum {
147 namespace: Option<String>,
148 type_: String,
149 },
150 Error {
151 namespace: Option<String>,
152 type_: String,
153 },
154 Flag {
155 namespace: Option<String>,
156 type_: String,
157 },
158 Func {
159 namespace: Option<String>,
160 type_: Option<String>,
161 name: String,
162 },
163 Interface {
164 namespace: Option<String>,
165 type_: String,
166 },
167 Method {
168 namespace: Option<String>,
169 type_: String,
170 name: String,
171 is_class_method: bool, },
173 Property {
174 namespace: Option<String>,
175 type_: String,
176 name: String,
177 },
178 Signal {
179 namespace: Option<String>,
180 type_: String,
181 name: String,
182 },
183 Struct {
184 namespace: Option<String>,
185 type_: String,
186 },
187 VFunc {
188 namespace: Option<String>,
189 type_: String,
190 name: String,
191 },
192}
193
194fn ns_type_to_doc(namespace: &Option<String>, type_: &str) -> String {
195 if let Some(ns) = namespace {
196 format!("{ns}::{type_}")
197 } else {
198 type_.to_string()
199 }
200}
201
202fn find_virtual_method_by_name(
203 type_: Option<&str>,
204 namespace: Option<&str>,
205 name: &str,
206 env: &Env,
207 in_type: Option<(&TypeId, Option<LocationInObject>)>,
208) -> Option<String> {
209 find_method_or_function(
210 env,
211 in_type,
212 |f| {
213 f.name == mangle_keywords(name)
214 && namespace.as_ref().map_or(f.ns_id == MAIN_NAMESPACE, |n| {
215 &env.library.namespaces[f.ns_id as usize].name == n
216 })
217 },
218 |o| type_.is_none_or(|t| o.name == t && is_same_namespace(env, namespace, o.type_id)),
219 |_| false,
220 |_| false,
221 |_| false,
222 false,
223 true,
224 )
225}
226
227fn find_method_or_function_by_name(
228 type_: Option<&str>,
229 namespace: Option<&str>,
230 name: &str,
231 env: &Env,
232 in_type: Option<(&TypeId, Option<LocationInObject>)>,
233 is_class_method: bool,
234) -> Option<String> {
235 find_method_or_function(
236 env,
237 in_type,
238 |f| {
239 f.name == mangle_keywords(name)
240 && namespace.as_ref().map_or(f.ns_id == MAIN_NAMESPACE, |n| {
241 &env.library.namespaces[f.ns_id as usize].name == n
242 })
243 },
244 |o| type_.is_none_or(|t| o.name == t && is_same_namespace(env, namespace, o.type_id)),
245 |r| type_.is_none_or(|t| r.name == t && is_same_namespace(env, namespace, r.type_id)),
246 |e| type_.is_none_or(|t| e.name == t && is_same_namespace(env, namespace, e.type_id)),
247 |f| type_.is_none_or(|t| f.name == t && is_same_namespace(env, namespace, f.type_id)),
248 is_class_method,
249 false,
250 )
251}
252
253fn is_same_namespace(env: &Env, namespace: Option<&str>, type_id: TypeId) -> bool {
254 namespace
255 .as_ref()
256 .map_or(MAIN_NAMESPACE == type_id.ns_id, |n| {
257 &env.library.namespaces[type_id.ns_id as usize].name == n
258 })
259}
260
261impl GiDocgen {
262 pub fn rust_link(
263 &self,
264 env: &Env,
265 in_type: Option<(&TypeId, Option<LocationInObject>)>,
266 ) -> String {
267 let symbols = env.symbols.borrow();
268 match self {
269 GiDocgen::Enum { type_, namespace } | GiDocgen::Error { type_, namespace } => env
270 .analysis
271 .enumerations
272 .iter()
273 .find(|e| &e.name == type_)
274 .map_or_else(
275 || format!("`{}`", ns_type_to_doc(namespace, type_)),
276 |info| gen_symbol_doc_link(info.type_id, env),
277 ),
278 GiDocgen::Class { type_, namespace } | GiDocgen::Interface { type_, namespace } => env
279 .analysis
280 .objects
281 .values()
282 .find(|o| {
283 &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id)
284 })
285 .map_or_else(
286 || format!("`{}`", ns_type_to_doc(namespace, type_)),
287 |info| gen_symbol_doc_link(info.type_id, env),
288 ),
289 GiDocgen::Flag { type_, namespace } => env
290 .analysis
291 .flags
292 .iter()
293 .find(|e| {
294 &e.name == type_ && is_same_namespace(env, namespace.as_deref(), e.type_id)
295 })
296 .map_or_else(
297 || format!("`{}`", ns_type_to_doc(namespace, type_)),
298 |info| gen_symbol_doc_link(info.type_id, env),
299 ),
300 GiDocgen::Const { type_, namespace } => env
301 .analysis
302 .constants
303 .iter()
304 .find(|c| &c.name == type_ && is_same_namespace(env, namespace.as_deref(), c.typ))
305 .map_or_else(
306 || format!("`{}`", ns_type_to_doc(namespace, type_)),
307 gen_const_doc_link,
308 ),
309 GiDocgen::Property {
310 type_,
311 name,
312 namespace,
313 } => env
314 .analysis
315 .objects
316 .values()
317 .find(|o| {
318 &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id)
319 })
320 .map_or_else(
321 || gen_property_doc_link(&ns_type_to_doc(namespace, type_), name),
322 |info| {
323 let sym = symbols.by_tid(info.type_id).unwrap();
324 gen_property_doc_link(&sym.full_rust_name(), name)
325 },
326 ),
327 GiDocgen::Signal {
328 type_,
329 name,
330 namespace,
331 } => env
332 .analysis
333 .objects
334 .values()
335 .find(|o| {
336 &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id)
337 })
338 .map_or_else(
339 || gen_signal_doc_link(&ns_type_to_doc(namespace, type_), name),
340 |info| {
341 let sym = symbols.by_tid(info.type_id).unwrap();
342 gen_signal_doc_link(&sym.full_rust_name(), name)
343 },
344 ),
345 GiDocgen::Id(c_name) => symbols.by_c_name(c_name).map_or_else(
346 || format!("`{c_name}`"),
347 |sym| format!("[`{n}`][crate::{n}]", n = sym.full_rust_name()),
348 ),
349 GiDocgen::Struct { namespace, type_ } => env
350 .analysis
351 .records
352 .values()
353 .find(|r| {
354 &r.name == type_ && is_same_namespace(env, namespace.as_deref(), r.type_id)
355 })
356 .map_or_else(
357 || format!("`{}`", ns_type_to_doc(namespace, type_)),
358 |info| gen_symbol_doc_link(info.type_id, env),
359 ),
360 GiDocgen::Constructor {
361 namespace,
362 type_,
363 name,
364 } => env
365 .analysis
366 .find_object_by_function(
367 env,
368 |o| &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id),
369 |f| f.name == mangle_keywords(name),
370 )
371 .map_or_else(
372 || format!("`{}::{}()`", ns_type_to_doc(namespace, type_), name),
373 |(obj_info, fn_info)| {
374 gen_object_fn_doc_link(obj_info, fn_info, env, in_type, type_)
375 },
376 ),
377 GiDocgen::Func {
378 namespace,
379 type_,
380 name,
381 } => find_method_or_function_by_name(
382 type_.as_deref(),
383 namespace.as_deref(),
384 name,
385 env,
386 in_type,
387 false,
388 )
389 .unwrap_or_else(|| {
390 if let Some(ty) = type_ {
391 format!("`{}::{}()`", ns_type_to_doc(namespace, ty), name)
392 } else {
393 format!("`{name}()`")
394 }
395 }),
396 GiDocgen::Alias(alias) => gen_alias_doc_link(alias),
397 GiDocgen::Method {
398 namespace,
399 type_,
400 name,
401 is_class_method,
402 } => find_method_or_function_by_name(
403 Some(type_),
404 namespace.as_deref(),
405 name,
406 env,
407 in_type,
408 *is_class_method,
409 )
410 .unwrap_or_else(|| format!("`{}::{}()`", ns_type_to_doc(namespace, type_), name)),
411 GiDocgen::Callback { namespace, name } => {
412 gen_callback_doc_link(&ns_type_to_doc(namespace, name))
413 }
414 GiDocgen::VFunc {
415 namespace,
416 type_,
417 name,
418 } => find_virtual_method_by_name(Some(type_), namespace.as_deref(), name, env, in_type)
419 .unwrap_or_else(|| gen_vfunc_doc_link(&ns_type_to_doc(namespace, type_), name)),
420 }
421 }
422}
423
424impl FromStr for GiDocgen {
425 type Err = GiDocgenError;
426 fn from_str(item_link: &str) -> Result<Self, Self::Err> {
428 let item_link = item_link.trim_start_matches('[').trim_end_matches(']');
429 if let Some((link_type, link_details)) = item_link.split_once('@') {
430 match link_type {
431 "alias" => Ok(Self::Alias(link_details.to_string())),
432 "class" => {
433 let (namespace, type_) = namespace_type_from_details(link_details, "class")?;
434 Ok(Self::Class { namespace, type_ })
435 }
436 "const" => {
437 let (namespace, type_) = namespace_type_from_details(link_details, "const")?;
438 Ok(Self::Const { namespace, type_ })
439 }
440 "ctor" => {
441 let (namespace, type_, name) =
442 namespace_type_method_from_details(link_details, "ctor", false)?;
443 Ok(Self::Constructor {
444 namespace,
445 type_: type_
446 .ok_or_else(|| GiDocgenError::BrokenLinkType("ctor".to_string()))?,
447 name,
448 })
449 }
450 "enum" => {
451 let (namespace, type_) = namespace_type_from_details(link_details, "enum")?;
452 Ok(Self::Enum { namespace, type_ })
453 }
454 "error" => {
455 let (namespace, type_) = namespace_type_from_details(link_details, "error")?;
456 Ok(Self::Error { namespace, type_ })
457 }
458 "flags" => {
459 let (namespace, type_) = namespace_type_from_details(link_details, "flags")?;
460 Ok(Self::Flag { namespace, type_ })
461 }
462 "func" => {
463 let (namespace, type_, name) =
464 namespace_type_method_from_details(link_details, "func", true)?;
465 Ok(Self::Func {
466 namespace,
467 type_,
468 name,
469 })
470 }
471 "iface" => {
472 let (namespace, type_) = namespace_type_from_details(link_details, "iface")?;
473 Ok(Self::Interface { namespace, type_ })
474 }
475 "callback" => {
476 let (namespace, name) = namespace_type_from_details(link_details, "callback")?;
477 Ok(Self::Callback { namespace, name })
478 }
479 "method" => {
480 let (namespace, type_, name) =
481 namespace_type_method_from_details(link_details, "method", false)?;
482 let type_ =
483 type_.ok_or_else(|| GiDocgenError::BrokenLinkType("method".to_string()))?;
484 Ok(Self::Method {
485 namespace,
486 is_class_method: type_.ends_with("Class"),
487 type_,
488 name,
489 })
490 }
491 "property" => {
492 let (namespace, type_) = namespace_type_from_details(link_details, "property")?;
493 let type_details: Vec<_> = type_.split(':').collect();
494 if type_details.len() < 2 || type_details[1].is_empty() {
495 Err(GiDocgenError::BrokenLinkType("property".to_string()))
496 } else {
497 Ok(Self::Property {
498 namespace,
499 type_: type_details[0].to_string(),
500 name: type_details[1].to_string(),
501 })
502 }
503 }
504 "signal" => {
505 let (namespace, type_) = namespace_type_from_details(link_details, "signal")?;
506 let type_details: Vec<_> = type_.split("::").collect();
507 if type_details.len() < 2 || type_details[1].is_empty() {
508 Err(GiDocgenError::BrokenLinkType("signal".to_string()))
509 } else {
510 Ok(Self::Signal {
511 namespace,
512 type_: type_details[0].to_string(),
513 name: type_details[1].to_string(),
514 })
515 }
516 }
517 "struct" => {
518 let (namespace, type_) = namespace_type_from_details(link_details, "struct")?;
519 Ok(Self::Struct { namespace, type_ })
520 }
521 "vfunc" => {
522 let (namespace, type_, name) =
523 namespace_type_method_from_details(link_details, "vfunc", false)?;
524 Ok(Self::VFunc {
525 namespace,
526 type_: type_
527 .ok_or_else(|| GiDocgenError::BrokenLinkType("vfunc".to_string()))?,
528 name,
529 })
530 }
531 "id" => Ok(Self::Id(link_details.to_string())),
532 e => Err(GiDocgenError::InvalidLinkType(e.to_string())),
533 }
534 } else {
535 Err(GiDocgenError::InvalidLink)
536 }
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543 #[test]
544 fn test_link_alias() {
545 assert_eq!(
546 GiDocgen::from_str("[alias@Allocation]"),
547 Ok(GiDocgen::Alias("Allocation".to_string()))
548 );
549 }
550
551 #[test]
552 fn test_link_class() {
553 assert_eq!(
554 GiDocgen::from_str("[class@Widget]"),
555 Ok(GiDocgen::Class {
556 namespace: None,
557 type_: "Widget".to_string(),
558 })
559 );
560 assert_eq!(
561 GiDocgen::from_str("[class@Gdk.Surface]"),
562 Ok(GiDocgen::Class {
563 namespace: Some("Gdk".to_string()),
564 type_: "Surface".to_string(),
565 })
566 );
567 assert_eq!(
568 GiDocgen::from_str("[class@Gsk.RenderNode]"),
569 Ok(GiDocgen::Class {
570 namespace: Some("Gsk".to_string()),
571 type_: "RenderNode".to_string(),
572 })
573 );
574
575 assert_eq!(
576 GiDocgen::from_str("[class@Gsk.RenderNode.test]"),
577 Err(GiDocgenError::BrokenLinkType("class".to_string()))
578 );
579
580 assert_eq!(
581 GiDocgen::from_str("[class@Gsk.]"),
582 Err(GiDocgenError::BrokenLinkType("class".to_string()))
583 );
584 }
585
586 #[test]
587 fn test_link_id() {
588 assert_eq!(
589 GiDocgen::from_str("[id@gtk_widget_show]"),
590 Ok(GiDocgen::Id("gtk_widget_show".to_string()))
591 );
592 }
593
594 #[test]
595 fn test_link_const() {
596 assert_eq!(
597 GiDocgen::from_str("[const@Gdk.KEY_q]"),
598 Ok(GiDocgen::Const {
599 namespace: Some("Gdk".to_string()),
600 type_: "KEY_q".to_string()
601 })
602 );
603 }
604
605 #[test]
606 fn test_link_callback() {
607 assert_eq!(
608 GiDocgen::from_str("[callback@Gtk.MapListModelMapFunc]"),
609 Ok(GiDocgen::Callback {
610 namespace: Some("Gtk".to_string()),
611 name: "MapListModelMapFunc".to_string()
612 })
613 );
614 }
615
616 #[test]
617 fn test_link_enum() {
618 assert_eq!(
619 GiDocgen::from_str("[enum@Orientation]"),
620 Ok(GiDocgen::Enum {
621 namespace: None,
622 type_: "Orientation".to_string()
623 })
624 );
625 }
626
627 #[test]
628 fn test_link_error() {
629 assert_eq!(
630 GiDocgen::from_str("[error@Gtk.BuilderParseError]"),
631 Ok(GiDocgen::Error {
632 namespace: Some("Gtk".to_string()),
633 type_: "BuilderParseError".to_string()
634 })
635 );
636 }
637
638 #[test]
639 fn test_link_flags() {
640 assert_eq!(
641 GiDocgen::from_str("[flags@Gdk.ModifierType]"),
642 Ok(GiDocgen::Flag {
643 namespace: Some("Gdk".to_string()),
644 type_: "ModifierType".to_string()
645 })
646 );
647 }
648
649 #[test]
650 fn test_link_iface() {
651 assert_eq!(
652 GiDocgen::from_str("[iface@Gtk.Buildable]"),
653 Ok(GiDocgen::Interface {
654 namespace: Some("Gtk".to_string()),
655 type_: "Buildable".to_string()
656 })
657 );
658 }
659
660 #[test]
661 fn test_link_struct() {
662 assert_eq!(
663 GiDocgen::from_str("[struct@Gtk.TextIter]"),
664 Ok(GiDocgen::Struct {
665 namespace: Some("Gtk".to_string()),
666 type_: "TextIter".to_string()
667 })
668 );
669 }
670
671 #[test]
672 fn test_link_property() {
673 assert_eq!(
674 GiDocgen::from_str("[property@Gtk.Orientable:orientation]"),
675 Ok(GiDocgen::Property {
676 namespace: Some("Gtk".to_string()),
677 type_: "Orientable".to_string(),
678 name: "orientation".to_string(),
679 })
680 );
681
682 assert_eq!(
683 GiDocgen::from_str("[property@Gtk.Orientable]"),
684 Err(GiDocgenError::BrokenLinkType("property".to_string()))
685 );
686
687 assert_eq!(
688 GiDocgen::from_str("[property@Gtk.Orientable:]"),
689 Err(GiDocgenError::BrokenLinkType("property".to_string()))
690 );
691 }
692
693 #[test]
694 fn test_link_signal() {
695 assert_eq!(
696 GiDocgen::from_str("[signal@Gtk.RecentManager::changed]"),
697 Ok(GiDocgen::Signal {
698 namespace: Some("Gtk".to_string()),
699 type_: "RecentManager".to_string(),
700 name: "changed".to_string(),
701 })
702 );
703
704 assert_eq!(
705 GiDocgen::from_str("[signal@Gtk.RecentManager]"),
706 Err(GiDocgenError::BrokenLinkType("signal".to_string()))
707 );
708
709 assert_eq!(
710 GiDocgen::from_str("[signal@Gtk.RecentManager::]"),
711 Err(GiDocgenError::BrokenLinkType("signal".to_string()))
712 );
713
714 assert_eq!(
715 GiDocgen::from_str("[signal@Gtk.RecentManager:]"),
716 Err(GiDocgenError::BrokenLinkType("signal".to_string()))
717 );
718 }
719
720 #[test]
721 fn test_link_vfunc() {
722 assert_eq!(
723 GiDocgen::from_str("[vfunc@Gtk.Widget.measure]"),
724 Ok(GiDocgen::VFunc {
725 namespace: Some("Gtk".to_string()),
726 type_: "Widget".to_string(),
727 name: "measure".to_string(),
728 })
729 );
730
731 assert_eq!(
732 GiDocgen::from_str("[vfunc@Widget.snapshot]"),
733 Ok(GiDocgen::VFunc {
734 namespace: None,
735 type_: "Widget".to_string(),
736 name: "snapshot".to_string(),
737 })
738 );
739 }
740
741 #[test]
742 fn test_link_ctor() {
743 assert_eq!(
744 GiDocgen::from_str("[ctor@Gtk.Box.new]"),
745 Ok(GiDocgen::Constructor {
746 namespace: Some("Gtk".to_string()),
747 type_: "Box".to_string(),
748 name: "new".to_string(),
749 })
750 );
751
752 assert_eq!(
753 GiDocgen::from_str("[ctor@Button.new_with_label]"),
754 Ok(GiDocgen::Constructor {
755 namespace: None,
756 type_: "Button".to_string(),
757 name: "new_with_label".to_string(),
758 })
759 );
760 }
761
762 #[test]
763 fn test_link_func() {
764 assert_eq!(
765 GiDocgen::from_str("[func@Gtk.init]"),
766 Ok(GiDocgen::Func {
767 namespace: Some("Gtk".to_string()),
768 type_: None,
769 name: "init".to_string(),
770 })
771 );
772
773 assert_eq!(
774 GiDocgen::from_str("[func@show_uri]"),
775 Ok(GiDocgen::Func {
776 namespace: None,
777 type_: None,
778 name: "show_uri".to_string(),
779 })
780 );
781
782 assert_eq!(
783 GiDocgen::from_str("[func@Gtk.Window.list_toplevels]"),
784 Ok(GiDocgen::Func {
785 namespace: Some("Gtk".to_string()),
786 type_: Some("Window".to_string()),
787 name: "list_toplevels".to_string(),
788 })
789 );
790 }
791
792 #[test]
793 fn test_link_method() {
794 assert_eq!(
795 GiDocgen::from_str("[method@Gtk.Widget.show]"),
796 Ok(GiDocgen::Method {
797 namespace: Some("Gtk".to_string()),
798 type_: "Widget".to_string(),
799 name: "show".to_string(),
800 is_class_method: false,
801 })
802 );
803
804 assert_eq!(
805 GiDocgen::from_str("[method@WidgetClass.add_binding]"),
806 Ok(GiDocgen::Method {
807 namespace: None,
808 type_: "WidgetClass".to_string(),
809 name: "add_binding".to_string(),
810 is_class_method: true,
811 })
812 );
813 }
814}