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#[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#[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#[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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
258#[serde(rename_all = "kebab-case")]
259pub enum SignalAnnotation {
260 Emitter(String),
261}
262
263#[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
772fn 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 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}