Skip to main content

gobject_ast/model/
doc.rs

1use serde::Serialize;
2use tree_sitter::Node;
3
4use super::Comment;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum TransferKind {
9    None,
10    Full,
11    Container,
12    Floating,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
16#[serde(rename_all = "kebab-case")]
17pub enum ScopeKind {
18    Call,
19    Async,
20    Notified,
21    Forever,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
25pub struct ArrayAnnotation {
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub length: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub fixed_size: Option<u32>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub zero_terminated: Option<bool>,
32}
33
34/// Annotations valid on function parameters.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
36#[serde(rename_all = "kebab-case")]
37pub enum ParamAnnotation {
38    Transfer(TransferKind),
39    Nullable,
40    NotNullable,
41    Optional,
42    AllowNone,
43    NotOptional,
44    In,
45    Out,
46    OutCallerAllocates,
47    OutCalleeAllocates,
48    Inout,
49    Array,
50    ArrayDetailed(ArrayAnnotation),
51    ElementType(Vec<String>),
52    Scope(ScopeKind),
53    Closure,
54    ClosureFor(String),
55    Destroy(String),
56    Type(String),
57    Skip,
58    Default(String),
59    Attributes(Vec<(String, String)>),
60    Unknown(String),
61}
62
63impl ParamAnnotation {
64    pub fn parse(name: &str, value: Option<&str>) -> Self {
65        match name {
66            "transfer" => match parse_transfer(value) {
67                Ok(t) => Self::Transfer(t),
68                Err(e) => {
69                    tracing::warn!("doc: {e}");
70                    Self::Unknown(format_annotation(name, value))
71                }
72            },
73            "nullable" => Self::Nullable,
74            "not nullable" => Self::NotNullable,
75            "optional" => Self::Optional,
76            "allow-none" => Self::AllowNone,
77            "not optional" => Self::NotOptional,
78            "caller-allocates" => Self::OutCallerAllocates,
79            "callee-allocates" => Self::OutCalleeAllocates,
80            "in" => Self::In,
81            "out" => match value {
82                None => Self::Out,
83                Some("caller-allocates") => Self::OutCallerAllocates,
84                Some("callee-allocates") => Self::OutCalleeAllocates,
85                Some(v) => {
86                    tracing::warn!("doc: unknown out modifier: {v:?}");
87                    Self::Unknown(format_annotation(name, value))
88                }
89            },
90            "inout" | "in-out" => Self::Inout,
91            "array" => match value {
92                Some(v) => Self::ArrayDetailed(parse_array(v)),
93                None => Self::Array,
94            },
95            "element-type" => match value {
96                Some(v) => Self::ElementType(v.split_whitespace().map(String::from).collect()),
97                None => {
98                    tracing::warn!("doc: element-type requires at least one type");
99                    Self::Unknown(format_annotation(name, value))
100                }
101            },
102            "scope" => match parse_scope(value) {
103                Ok(s) => Self::Scope(s),
104                Err(e) => {
105                    tracing::warn!("doc: {e}");
106                    Self::Unknown(format_annotation(name, value))
107                }
108            },
109            "closure" => match value {
110                Some(v) => Self::ClosureFor(v.to_owned()),
111                None => Self::Closure,
112            },
113            "destroy" => match value {
114                Some(v) => Self::Destroy(v.to_owned()),
115                None => {
116                    tracing::warn!("doc: destroy requires a parameter name");
117                    Self::Unknown(format_annotation(name, value))
118                }
119            },
120            "type" => match value {
121                Some(v) => Self::Type(v.to_owned()),
122                None => {
123                    tracing::warn!("doc: type requires a type name");
124                    Self::Unknown(format_annotation(name, value))
125                }
126            },
127            "skip" => Self::Skip,
128            "attributes" => Self::Attributes(parse_attributes(value)),
129            "default" => match value {
130                Some(v) => Self::Default(v.to_owned()),
131                None => {
132                    tracing::warn!("doc: default requires a value");
133                    Self::Unknown(format_annotation(name, value))
134                }
135            },
136            _ => {
137                tracing::warn!(
138                    "doc: unknown param annotation: ({})",
139                    format_annotation(name, value)
140                );
141                Self::Unknown(format_annotation(name, value))
142            }
143        }
144    }
145}
146
147/// Annotations valid on return values.
148#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
149#[serde(rename_all = "kebab-case")]
150pub enum ReturnAnnotation {
151    Transfer(TransferKind),
152    Nullable,
153    NotNullable,
154    Optional,
155    Skip,
156    Array,
157    ArrayDetailed(ArrayAnnotation),
158    ElementType(Vec<String>),
159    Type(String),
160    Attributes(Vec<(String, String)>),
161    Unknown(String),
162}
163
164impl ReturnAnnotation {
165    pub fn parse(name: &str, value: Option<&str>) -> Self {
166        match name {
167            "transfer" => match parse_transfer(value) {
168                Ok(t) => Self::Transfer(t),
169                Err(e) => {
170                    tracing::warn!("doc: {e}");
171                    Self::Unknown(format_annotation(name, value))
172                }
173            },
174            "nullable" => Self::Nullable,
175            "not nullable" => Self::NotNullable,
176            "optional" => Self::Optional,
177            "skip" => Self::Skip,
178            "array" => match value {
179                Some(v) => Self::ArrayDetailed(parse_array(v)),
180                None => Self::Array,
181            },
182            "element-type" => match value {
183                Some(v) => Self::ElementType(v.split_whitespace().map(String::from).collect()),
184                None => {
185                    tracing::warn!("doc: element-type requires at least one type");
186                    Self::Unknown(format_annotation(name, value))
187                }
188            },
189            "type" => match value {
190                Some(v) => Self::Type(v.to_owned()),
191                None => {
192                    tracing::warn!("doc: type requires a type name");
193                    Self::Unknown(format_annotation(name, value))
194                }
195            },
196            "attributes" => Self::Attributes(parse_attributes(value)),
197            _ => {
198                tracing::warn!(
199                    "doc: unknown return annotation: ({})",
200                    format_annotation(name, value)
201                );
202                Self::Unknown(format_annotation(name, value))
203            }
204        }
205    }
206
207    pub fn transfer(&self) -> Option<&TransferKind> {
208        if let Self::Transfer(k) = self {
209            Some(k)
210        } else {
211            None
212        }
213    }
214}
215
216/// Annotations valid on functions/methods.
217#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
218#[serde(rename_all = "kebab-case")]
219pub enum FunctionAnnotation {
220    Skip,
221    Constructor,
222    Method,
223    Virtual(String),
224    SetProperty(String),
225    GetProperty(String),
226    RenameTo(String),
227    SyncFunc(String),
228    AsyncFunc(String),
229    FinishFunc(String),
230}
231
232/// Annotations valid on type declarations (structs, boxed, fundamental).
233#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
234#[serde(rename_all = "kebab-case")]
235pub enum TypeAnnotation {
236    Skip,
237    Foreign,
238    RenameTo(String),
239    RefFunc(String),
240    UnrefFunc(String),
241    CopyFunc(String),
242    FreeFunc(String),
243    GetValueFunc(String),
244    SetValueFunc(String),
245}
246
247/// Annotations valid on properties.
248#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
249#[serde(rename_all = "kebab-case")]
250pub enum PropertyAnnotation {
251    Getter(String),
252    Setter(String),
253    DefaultValue(String),
254}
255
256/// Annotations valid on signals.
257#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
258#[serde(rename_all = "kebab-case")]
259pub enum SignalAnnotation {
260    Emitter(String),
261}
262
263/// Annotations valid on enum/flag values.
264#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
265#[serde(rename_all = "kebab-case")]
266pub enum EnumValueAnnotation {
267    Value(String),
268}
269
270fn parse_transfer(value: Option<&str>) -> Result<TransferKind, String> {
271    match value {
272        Some("none") => Ok(TransferKind::None),
273        Some("full") => Ok(TransferKind::Full),
274        Some("container") => Ok(TransferKind::Container),
275        Some("floating") => Ok(TransferKind::Floating),
276        _ => Err(format!("unknown transfer kind: {:?}", value)),
277    }
278}
279
280fn parse_scope(value: Option<&str>) -> Result<ScopeKind, String> {
281    match value {
282        Some("call") => Ok(ScopeKind::Call),
283        Some("async") => Ok(ScopeKind::Async),
284        Some("notified") => Ok(ScopeKind::Notified),
285        Some("forever") => Ok(ScopeKind::Forever),
286        _ => Err(format!("unknown scope kind: {:?}", value)),
287    }
288}
289
290fn parse_array(value: &str) -> ArrayAnnotation {
291    let mut length = None;
292    let mut fixed_size = None;
293    let mut zero_terminated = None;
294
295    for part in value.split_whitespace() {
296        if let Some(v) = part.strip_prefix("length=") {
297            length = Some(v.to_owned());
298        } else if let Some(v) = part.strip_prefix("fixed-size=") {
299            fixed_size = v.parse().ok();
300        } else if let Some(v) = part.strip_prefix("zero-terminated=") {
301            zero_terminated = match v {
302                "1" => Some(true),
303                "0" => Some(false),
304                _ => None,
305            };
306        }
307    }
308
309    ArrayAnnotation {
310        length,
311        fixed_size,
312        zero_terminated,
313    }
314}
315
316fn parse_attributes(value: Option<&str>) -> Vec<(String, String)> {
317    value
318        .unwrap_or("")
319        .split_whitespace()
320        .filter_map(|kv| {
321            let (k, v) = kv.split_once('=')?;
322            Some((k.to_owned(), v.to_owned()))
323        })
324        .collect()
325}
326
327fn format_annotation(name: &str, value: Option<&str>) -> String {
328    match value {
329        Some(v) => format!("{name} {v}"),
330        None => name.to_owned(),
331    }
332}
333
334fn parse_value_annotation<A>(
335    name: &str,
336    value: Option<&str>,
337    label: &str,
338    f: fn(String) -> A,
339) -> Option<A> {
340    match value {
341        Some(v) => Some(f(v.to_owned())),
342        None => {
343            tracing::warn!("doc: ({name}) requires {label}");
344            None
345        }
346    }
347}
348
349fn parse_function_annotation(name: &str, value: Option<&str>) -> Option<FunctionAnnotation> {
350    match name {
351        "skip" => Some(FunctionAnnotation::Skip),
352        "constructor" => Some(FunctionAnnotation::Constructor),
353        "method" => Some(FunctionAnnotation::Method),
354        "virtual" => {
355            parse_value_annotation(name, value, "a slot name", FunctionAnnotation::Virtual)
356        }
357        "set-property" => parse_value_annotation(
358            name,
359            value,
360            "a property name",
361            FunctionAnnotation::SetProperty,
362        ),
363        "get-property" => parse_value_annotation(
364            name,
365            value,
366            "a property name",
367            FunctionAnnotation::GetProperty,
368        ),
369        "rename-to" => {
370            parse_value_annotation(name, value, "a symbol name", FunctionAnnotation::RenameTo)
371        }
372        "sync-func" => {
373            parse_value_annotation(name, value, "a function name", FunctionAnnotation::SyncFunc)
374        }
375        "async-func" => parse_value_annotation(
376            name,
377            value,
378            "a function name",
379            FunctionAnnotation::AsyncFunc,
380        ),
381        "finish-func" => parse_value_annotation(
382            name,
383            value,
384            "a function name",
385            FunctionAnnotation::FinishFunc,
386        ),
387        _ => None,
388    }
389}
390
391fn parse_type_annotation(name: &str, value: Option<&str>) -> Option<TypeAnnotation> {
392    match name {
393        "skip" => Some(TypeAnnotation::Skip),
394        "foreign" => Some(TypeAnnotation::Foreign),
395        "rename-to" => {
396            parse_value_annotation(name, value, "a symbol name", TypeAnnotation::RenameTo)
397        }
398        "ref-func" => {
399            parse_value_annotation(name, value, "a function name", TypeAnnotation::RefFunc)
400        }
401        "unref-func" => {
402            parse_value_annotation(name, value, "a function name", TypeAnnotation::UnrefFunc)
403        }
404        "copy-func" => {
405            parse_value_annotation(name, value, "a function name", TypeAnnotation::CopyFunc)
406        }
407        "free-func" => {
408            parse_value_annotation(name, value, "a function name", TypeAnnotation::FreeFunc)
409        }
410        "get-value-func" => {
411            parse_value_annotation(name, value, "a function name", TypeAnnotation::GetValueFunc)
412        }
413        "set-value-func" => {
414            parse_value_annotation(name, value, "a function name", TypeAnnotation::SetValueFunc)
415        }
416        _ => None,
417    }
418}
419
420fn parse_property_annotation(name: &str, value: Option<&str>) -> Option<PropertyAnnotation> {
421    match name {
422        "getter" => {
423            parse_value_annotation(name, value, "a symbol name", PropertyAnnotation::Getter)
424        }
425        "setter" => {
426            parse_value_annotation(name, value, "a symbol name", PropertyAnnotation::Setter)
427        }
428        "default-value" => {
429            parse_value_annotation(name, value, "a value", PropertyAnnotation::DefaultValue)
430        }
431        _ => None,
432    }
433}
434
435fn parse_signal_annotation(name: &str, value: Option<&str>) -> Option<SignalAnnotation> {
436    match name {
437        "emitter" => {
438            parse_value_annotation(name, value, "a method name", SignalAnnotation::Emitter)
439        }
440        _ => None,
441    }
442}
443
444fn parse_enum_value_annotation(name: &str, value: Option<&str>) -> Option<EnumValueAnnotation> {
445    match name {
446        "value" => parse_value_annotation(name, value, "a value", EnumValueAnnotation::Value),
447        _ => None,
448    }
449}
450
451#[derive(Debug, Clone, Serialize)]
452pub struct DocParam {
453    pub name: String,
454    #[serde(skip_serializing_if = "Vec::is_empty")]
455    pub annotations: Vec<ParamAnnotation>,
456    #[serde(skip_serializing_if = "String::is_empty")]
457    pub description: String,
458}
459
460#[derive(Debug, Clone, Serialize)]
461pub struct DocReturns {
462    #[serde(skip_serializing_if = "Vec::is_empty")]
463    pub annotations: Vec<ReturnAnnotation>,
464    #[serde(skip_serializing_if = "String::is_empty")]
465    pub description: String,
466}
467
468struct RawDoc<A> {
469    symbol: Option<String>,
470    annotations: Vec<A>,
471    params: Vec<DocParam>,
472    returns: Option<DocReturns>,
473    description: Vec<String>,
474    since: Option<String>,
475    deprecated: Option<String>,
476}
477
478impl<A> RawDoc<A> {
479    fn from_node(
480        node: Node<'_>,
481        source: &[u8],
482        parse_annotation: fn(&str, Option<&str>) -> Option<A>,
483    ) -> Option<Self> {
484        let prev = node.prev_named_sibling()?;
485        if prev.kind() != "comment" {
486            return None;
487        }
488        let text = std::str::from_utf8(&source[prev.byte_range()]).ok()?;
489        if !text.starts_with("/**") {
490            return None;
491        }
492        Self::from_text(text, parse_annotation)
493    }
494
495    fn from_comment(
496        comment: &Comment,
497        parse_annotation: fn(&str, Option<&str>) -> Option<A>,
498    ) -> Option<Self> {
499        if !comment.is_gtk_doc() {
500            return None;
501        }
502        Self::from_text(&comment.text, parse_annotation)
503    }
504
505    fn from_text(
506        text: &str,
507        parse_annotation: fn(&str, Option<&str>) -> Option<A>,
508    ) -> Option<Self> {
509        let text = text.strip_prefix("/**")?.strip_suffix("*/")?.trim();
510
511        let mut symbol = None;
512        let mut annotations = Vec::new();
513        let mut params = Vec::new();
514        let mut returns = None;
515        let mut description = Vec::new();
516        let mut since = None;
517        let mut deprecated = None;
518        let mut in_description = false;
519
520        for raw_line in text.lines() {
521            let line = raw_line.trim().strip_prefix('*').unwrap_or(raw_line.trim());
522            let line = line.strip_prefix(' ').unwrap_or(line);
523
524            if line.is_empty() {
525                continue;
526            }
527
528            if !in_description
529                && let Some(rest) = line.strip_prefix('@')
530                && let Some((name, after_colon)) = rest.split_once(':')
531            {
532                let (anns, desc) =
533                    parse_annotations_and_desc(after_colon.trim(), ParamAnnotation::parse);
534                params.push(DocParam {
535                    name: name.trim().to_owned(),
536                    annotations: anns,
537                    description: desc,
538                });
539            } else if let Some(after) = line.strip_prefix("Returns:") {
540                let (anns, desc) =
541                    parse_annotations_and_desc(after.trim(), ReturnAnnotation::parse);
542                returns = Some(DocReturns {
543                    annotations: anns,
544                    description: desc,
545                });
546            } else if let Some(v) = line.strip_prefix("Since:") {
547                since = Some(v.trim().to_owned());
548            } else if let Some(v) = line.strip_prefix("Deprecated:") {
549                deprecated = Some(v.trim().to_owned());
550            } else if symbol.is_none() && params.is_empty() && description.is_empty() {
551                let symbol_end = line
552                    .find(|c: char| !(c.is_alphanumeric() || c == '_' || c == ':' || c == '-'))
553                    .unwrap_or(line.len());
554                let candidate = &line[..symbol_end];
555                let rest = line[symbol_end..].trim();
556
557                let sym = candidate.trim_end_matches(':');
558                if !sym.is_empty()
559                    && sym
560                        .chars()
561                        .all(|c| c.is_alphanumeric() || c == '_' || c == ':' || c == '-')
562                {
563                    symbol = Some(sym.to_owned());
564                    if !rest.is_empty() {
565                        annotations = parse_symbol_annotations(rest, parse_annotation);
566                    }
567                } else {
568                    in_description = true;
569                    description.push(line.to_owned());
570                }
571            } else {
572                in_description = true;
573                description.push(line.to_owned());
574            }
575        }
576
577        Some(Self {
578            symbol,
579            annotations,
580            params,
581            returns,
582            description,
583            since,
584            deprecated,
585        })
586    }
587}
588
589#[derive(Debug, Clone, Serialize)]
590pub struct FunctionDoc {
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub symbol: Option<String>,
593    #[serde(skip_serializing_if = "Vec::is_empty")]
594    pub annotations: Vec<FunctionAnnotation>,
595    #[serde(skip_serializing_if = "Vec::is_empty")]
596    pub params: Vec<DocParam>,
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub returns: Option<DocReturns>,
599    #[serde(skip_serializing_if = "Vec::is_empty")]
600    pub description: Vec<String>,
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub since: Option<String>,
603    #[serde(skip_serializing_if = "Option::is_none")]
604    pub deprecated: Option<String>,
605}
606
607impl FunctionDoc {
608    pub fn from_node(node: Node<'_>, source: &[u8]) -> Option<Self> {
609        RawDoc::from_node(node, source, parse_function_annotation).map(Self::from_raw)
610    }
611
612    fn from_raw(raw: RawDoc<FunctionAnnotation>) -> Self {
613        Self {
614            symbol: raw.symbol,
615            annotations: raw.annotations,
616            params: raw.params,
617            returns: raw.returns,
618            description: raw.description,
619            since: raw.since,
620            deprecated: raw.deprecated,
621        }
622    }
623
624    pub fn param(&self, name: &str) -> Option<&DocParam> {
625        self.params.iter().find(|p| p.name == name)
626    }
627
628    pub fn param_has_annotation(&self, param: &str, annotation: &ParamAnnotation) -> bool {
629        self.param(param)
630            .is_some_and(|p| p.annotations.contains(annotation))
631    }
632
633    pub fn return_transfer(&self) -> Option<&TransferKind> {
634        self.returns
635            .as_ref()?
636            .annotations
637            .iter()
638            .find_map(|a| a.transfer())
639    }
640}
641
642#[derive(Debug, Clone, Serialize)]
643pub struct TypeDoc {
644    #[serde(skip_serializing_if = "Option::is_none")]
645    pub symbol: Option<String>,
646    #[serde(skip_serializing_if = "Vec::is_empty")]
647    pub annotations: Vec<TypeAnnotation>,
648    #[serde(skip_serializing_if = "Vec::is_empty")]
649    pub description: Vec<String>,
650    #[serde(skip_serializing_if = "Option::is_none")]
651    pub since: Option<String>,
652    #[serde(skip_serializing_if = "Option::is_none")]
653    pub deprecated: Option<String>,
654}
655
656impl TypeDoc {
657    pub fn from_node(node: Node<'_>, source: &[u8]) -> Option<Self> {
658        RawDoc::from_node(node, source, parse_type_annotation).map(Self::from_raw)
659    }
660
661    pub fn from_comment(comment: &Comment) -> Option<Self> {
662        RawDoc::from_comment(comment, parse_type_annotation).map(Self::from_raw)
663    }
664
665    fn from_raw(raw: RawDoc<TypeAnnotation>) -> Self {
666        Self {
667            symbol: raw.symbol,
668            annotations: raw.annotations,
669            description: raw.description,
670            since: raw.since,
671            deprecated: raw.deprecated,
672        }
673    }
674}
675
676#[derive(Debug, Clone, Serialize)]
677pub struct PropertyDoc {
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub symbol: Option<String>,
680    #[serde(skip_serializing_if = "Vec::is_empty")]
681    pub annotations: Vec<PropertyAnnotation>,
682    #[serde(skip_serializing_if = "Vec::is_empty")]
683    pub description: Vec<String>,
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub since: Option<String>,
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub deprecated: Option<String>,
688}
689
690impl PropertyDoc {
691    pub fn from_comment(comment: &Comment) -> Option<Self> {
692        RawDoc::from_comment(comment, parse_property_annotation).map(Self::from_raw)
693    }
694
695    fn from_raw(raw: RawDoc<PropertyAnnotation>) -> Self {
696        Self {
697            symbol: raw.symbol,
698            annotations: raw.annotations,
699            description: raw.description,
700            since: raw.since,
701            deprecated: raw.deprecated,
702        }
703    }
704}
705
706#[derive(Debug, Clone, Serialize)]
707pub struct SignalDoc {
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub symbol: Option<String>,
710    #[serde(skip_serializing_if = "Vec::is_empty")]
711    pub annotations: Vec<SignalAnnotation>,
712    #[serde(skip_serializing_if = "Vec::is_empty")]
713    pub params: Vec<DocParam>,
714    #[serde(skip_serializing_if = "Option::is_none")]
715    pub returns: Option<DocReturns>,
716    #[serde(skip_serializing_if = "Vec::is_empty")]
717    pub description: Vec<String>,
718    #[serde(skip_serializing_if = "Option::is_none")]
719    pub since: Option<String>,
720    #[serde(skip_serializing_if = "Option::is_none")]
721    pub deprecated: Option<String>,
722}
723
724impl SignalDoc {
725    pub fn from_comment(comment: &Comment) -> Option<Self> {
726        RawDoc::from_comment(comment, parse_signal_annotation).map(Self::from_raw)
727    }
728
729    fn from_raw(raw: RawDoc<SignalAnnotation>) -> Self {
730        Self {
731            symbol: raw.symbol,
732            annotations: raw.annotations,
733            params: raw.params,
734            returns: raw.returns,
735            description: raw.description,
736            since: raw.since,
737            deprecated: raw.deprecated,
738        }
739    }
740}
741
742#[derive(Debug, Clone, Serialize)]
743pub struct EnumValueDoc {
744    #[serde(skip_serializing_if = "Option::is_none")]
745    pub symbol: Option<String>,
746    #[serde(skip_serializing_if = "Vec::is_empty")]
747    pub annotations: Vec<EnumValueAnnotation>,
748    #[serde(skip_serializing_if = "Vec::is_empty")]
749    pub description: Vec<String>,
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub since: Option<String>,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub deprecated: Option<String>,
754}
755
756impl EnumValueDoc {
757    pub fn from_node(node: Node<'_>, source: &[u8]) -> Option<Self> {
758        RawDoc::from_node(node, source, parse_enum_value_annotation).map(Self::from_raw)
759    }
760
761    fn from_raw(raw: RawDoc<EnumValueAnnotation>) -> Self {
762        Self {
763            symbol: raw.symbol,
764            annotations: raw.annotations,
765            description: raw.description,
766            since: raw.since,
767            deprecated: raw.deprecated,
768        }
769    }
770}
771
772/// Parse `(annotation1) (annotation2): description text` using the
773/// provided parse function for the annotation type.
774fn is_annotation_name(name: &str) -> bool {
775    !name.is_empty()
776        && name
777            .chars()
778            .all(|c| c.is_ascii_lowercase() || c == '-' || c == ' ')
779}
780
781fn parse_annotations_and_desc<T>(
782    text: &str,
783    parse_fn: fn(&str, Option<&str>) -> T,
784) -> (Vec<T>, String) {
785    let mut annotations = Vec::new();
786    let mut rest = text;
787
788    // Annotations must appear consecutively at the start: (nullable)(transfer full)
789    while rest.starts_with('(') {
790        let Some(end) = rest.find(')') else {
791            break;
792        };
793        let inner = &rest[1..end];
794
795        let (name, value) = if let Some(rest_after) = inner.strip_prefix("not ") {
796            if let Some((_, v)) = rest_after.split_once(' ') {
797                (&inner[..inner.len() - v.len() - 1], Some(v))
798            } else {
799                (inner, None)
800            }
801        } else {
802            match inner.split_once(' ') {
803                Some((n, v)) => (n, Some(v)),
804                None => (inner, None),
805            }
806        };
807
808        if !is_annotation_name(name) {
809            break;
810        }
811
812        annotations.push(parse_fn(name, value));
813
814        rest = rest[end + 1..].trim_start();
815    }
816
817    let desc = rest.strip_prefix(':').unwrap_or(rest).trim();
818    (annotations, desc.to_owned())
819}
820
821fn parse_symbol_annotations<A>(
822    text: &str,
823    parse_fn: fn(&str, Option<&str>) -> Option<A>,
824) -> Vec<A> {
825    let mut annotations = Vec::new();
826    let mut rest = text;
827
828    while rest.starts_with('(') {
829        let Some(end) = rest.find(')') else {
830            break;
831        };
832        let inner = &rest[1..end];
833
834        let (name, value) = if let Some(rest_after) = inner.strip_prefix("not ") {
835            if let Some((_, v)) = rest_after.split_once(' ') {
836                (&inner[..inner.len() - v.len() - 1], Some(v))
837            } else {
838                (inner, None)
839            }
840        } else {
841            match inner.split_once(' ') {
842                Some((n, v)) => (n, Some(v)),
843                None => (inner, None),
844            }
845        };
846
847        if !is_annotation_name(name) {
848            break;
849        }
850
851        if let Some(a) = parse_fn(name, value) {
852            annotations.push(a);
853        }
854
855        rest = rest[end + 1..].trim_start();
856    }
857
858    annotations
859}