1use std::{
2 borrow::Cow,
3 collections::{BTreeSet, HashSet},
4 io::{Result, Write},
5 sync::OnceLock,
6};
7
8use log::{error, info};
9use regex::{Captures, Regex};
10use stripper_lib::{write_file_name, write_item_doc, Type as SType, TypeStruct};
11
12use self::format::reformat_doc;
13use crate::{
14 analysis::{self, namespaces::MAIN, object::LocationInObject},
15 config::gobjects::GObject,
16 env::Env,
17 file_saver::save_to_file,
18 library::{self, Type as LType, *},
19 nameutil,
20 traits::*,
21 version::Version,
22};
23
24mod format;
25mod gi_docgen;
26
27const IGNORED_C_FN_PARAMS: [&str; 6] = [
29 "user_data",
30 "user_destroy",
31 "destroy_func",
32 "dnotify",
33 "destroy",
34 "user_data_free_func",
35];
36
37trait ToStripperType {
38 fn to_stripper_type(&self) -> TypeStruct;
39}
40
41macro_rules! impl_to_stripper_type {
42 ($ty:ident, $enum_var:ident, $useless:expr) => {
43 impl ToStripperType for $ty {
44 fn to_stripper_type(&self) -> TypeStruct {
45 TypeStruct::new(
46 SType::$enum_var,
47 &format!(
48 "connect_{}",
49 nameutil::mangle_keywords(nameutil::signal_to_snake(&self.name))
50 ),
51 )
52 }
53 }
54 };
55 ($ty:ident, $enum_var:ident) => {
56 impl ToStripperType for $ty {
57 fn to_stripper_type(&self) -> TypeStruct {
58 TypeStruct::new(SType::$enum_var, &nameutil::mangle_keywords(&self.name))
59 }
60 }
61 };
62}
63
64trait FunctionLikeType {
65 fn doc(&self) -> &Option<String>;
66 fn doc_deprecated(&self) -> &Option<String>;
67 fn ret(&self) -> &Parameter;
68 fn parameters(&self) -> &[Parameter];
69 fn deprecated_version(&self) -> &Option<Version>;
70}
71
72macro_rules! impl_function_like_type {
73 ($ty:ident) => {
74 impl FunctionLikeType for $ty {
75 fn doc(&self) -> &Option<String> {
76 &self.doc
77 }
78 fn doc_deprecated(&self) -> &Option<String> {
79 &self.doc_deprecated
80 }
81 fn ret(&self) -> &Parameter {
82 &self.ret
83 }
84 fn parameters(&self) -> &[Parameter] {
85 &self.parameters
86 }
87 fn deprecated_version(&self) -> &Option<Version> {
88 &self.deprecated_version
89 }
90 }
91 };
92}
93
94impl_to_stripper_type!(Enumeration, Enum);
95impl_to_stripper_type!(Bitfield, Struct);
96impl_to_stripper_type!(Record, Struct);
97impl_to_stripper_type!(Class, Struct);
98impl_to_stripper_type!(Function, Fn);
99impl_to_stripper_type!(Signal, Fn, false);
100
101impl_function_like_type!(Function);
102impl_function_like_type!(Signal);
103
104pub fn generate(env: &Env) {
105 info!("Generating documentation {:?}", env.config.doc_target_path);
106 save_to_file(&env.config.doc_target_path, env.config.make_backup, |w| {
107 generate_doc(w, env)
108 });
109}
110
111#[allow(clippy::type_complexity)]
112fn generate_doc(w: &mut dyn Write, env: &Env) -> Result<()> {
113 write_file_name(w, None)?;
114 let mut generators: Vec<(&str, Box<dyn Fn(&mut dyn Write, &Env) -> Result<()>>)> = Vec::new();
115
116 for info in env.analysis.objects.values() {
117 if info.type_id.ns_id == MAIN && !env.is_totally_deprecated(None, info.deprecated_version) {
118 generators.push((
119 &info.name,
120 Box::new(move |w, e| create_object_doc(w, e, info)),
121 ));
122 }
123 }
124
125 for info in env.analysis.records.values() {
126 if info.type_id.ns_id == MAIN && !env.is_totally_deprecated(None, info.deprecated_version) {
127 generators.push((
128 &info.name,
129 Box::new(move |w, e| create_record_doc(w, e, info)),
130 ));
131 }
132 }
133
134 for (tid, type_) in env.library.namespace_types(MAIN) {
135 if let LType::Enumeration(enum_) = type_ {
136 if !env
137 .config
138 .objects
139 .get(&tid.full_name(&env.library))
140 .is_none_or(|obj| obj.status.ignored())
141 && !env.is_totally_deprecated(None, enum_.deprecated_version)
142 {
143 generators.push((
144 enum_.name.as_str(),
145 Box::new(move |w, e| create_enum_doc(w, e, enum_, tid)),
146 ));
147 }
148 } else if let LType::Bitfield(bitfield) = type_ {
149 if !env
150 .config
151 .objects
152 .get(&tid.full_name(&env.library))
153 .is_none_or(|obj| obj.status.ignored())
154 && !env.is_totally_deprecated(None, bitfield.deprecated_version)
155 {
156 generators.push((
157 bitfield.name.as_str(),
158 Box::new(move |w, e| create_bitfield_doc(w, e, bitfield, tid)),
159 ));
160 }
161 }
162 }
163
164 let ns = env.library.namespace(library::MAIN_NAMESPACE);
165
166 if let Some(ref global_functions) = env.analysis.global_functions {
167 let functions = ns
168 .functions
169 .iter()
170 .filter(|f| f.kind == library::FunctionKind::Global);
171
172 for function in functions {
173 if let Some(ref c_identifier) = function.c_identifier {
174 let f_info = global_functions
175 .functions
176 .iter()
177 .find(move |f| &f.glib_name == c_identifier);
178 let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone());
179 let doc_trait_name = f_info.and_then(|f| f.doc_trait_name.as_ref());
180 let doc_struct_name = f_info.and_then(|f| f.doc_struct_name.as_ref());
181 assert!(
182 !(doc_trait_name.is_some() && doc_struct_name.is_some()),
183 "Can't use both doc_trait_name and doc_struct_name on the same function"
184 );
185
186 let parent = if doc_trait_name.is_some() {
187 doc_trait_name.map(|p| Box::new(TypeStruct::new(SType::Trait, p)))
188 } else if doc_struct_name.is_some() {
189 doc_struct_name.map(|p| Box::new(TypeStruct::new(SType::Impl, p)))
190 } else {
191 None
192 };
193
194 let doc_ignored_parameters = f_info
195 .map(|analyzed_f| analyzed_f.doc_ignore_parameters.clone())
196 .unwrap_or_default();
197
198 let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env));
199 if !should_be_documented {
200 continue;
201 }
202
203 create_fn_doc(
204 w,
205 env,
206 function,
207 parent,
208 fn_new_name,
209 &doc_ignored_parameters,
210 None,
211 f_info.is_none_or(|f| f.generate_doc),
212 )?;
213 }
214 }
215 }
216
217 for constant in &ns.constants {
218 let ty = if constant.c_type == "gchar*" {
220 SType::Static
221 } else {
222 SType::Const
223 };
224 let ty_id = TypeStruct::new(ty, &constant.name);
225 let generate_doc = env
226 .config
227 .objects
228 .get(&constant.typ.full_name(&env.library))
229 .is_none_or(|c| c.generate_doc);
230 if generate_doc {
231 write_item_doc(w, &ty_id, |w| {
232 if let Some(ref doc) = constant.doc {
233 writeln!(w, "{}", reformat_doc(doc, env, Some((&constant.typ, None))))?;
234 }
235 Ok(())
236 })?;
237 }
238 }
239
240 generators.sort_by_key(|&(name, _)| name);
241 for (_, f) in generators {
242 f(w, env)?;
243 }
244
245 Ok(())
246}
247
248fn create_object_doc(w: &mut dyn Write, env: &Env, info: &analysis::object::Info) -> Result<()> {
249 let ty = TypeStruct::new(SType::Struct, &info.name);
250 let ty_ext = TypeStruct::new(SType::Trait, &info.trait_name);
251 let has_trait = info.generate_trait;
252 let doc;
253 let doc_deprecated;
254 let functions: &[Function];
255 let virtual_methods: &[Function];
256 let signals: &[Signal];
257 let properties: &[Property];
258 let is_abstract;
259 let has_builder;
260
261 let obj = env
262 .config
263 .objects
264 .get(&info.full_name)
265 .expect("Object not found");
266
267 match env.library.type_(info.type_id) {
268 Type::Class(cl) => {
269 doc = cl.doc.as_ref();
270 doc_deprecated = cl.doc_deprecated.as_ref();
271 functions = &cl.functions;
272 virtual_methods = &cl.virtual_methods;
273 signals = &cl.signals;
274 properties = &cl.properties;
275 is_abstract = env.library.type_(info.type_id).is_abstract();
276 has_builder = obj.generate_builder;
277 }
278 Type::Interface(iface) => {
279 doc = iface.doc.as_ref();
280 doc_deprecated = iface.doc_deprecated.as_ref();
281 functions = &iface.functions;
282 virtual_methods = &iface.virtual_methods;
283 signals = &iface.signals;
284 properties = &iface.properties;
285 is_abstract = false;
286 has_builder = false;
287 }
288 _ => unreachable!(),
289 }
290
291 let manual_traits = get_type_manual_traits_for_implements(env, info);
292
293 write_item_doc(w, &ty, |w| {
294 if let Some(doc) = doc_deprecated {
295 writeln!(
296 w,
297 "{}",
298 reformat_doc(
299 doc,
300 env,
301 Some((&info.type_id, Some(LocationInObject::Impl)))
302 )
303 )?;
304 }
305 if let (Some(doc), true) = (doc, obj.generate_doc) {
306 writeln!(
307 w,
308 "{}",
309 reformat_doc(
310 doc,
311 env,
312 Some((&info.type_id, Some(LocationInObject::Impl)))
313 )
314 )?;
315 } else {
316 writeln!(w)?;
317 }
318 if is_abstract {
319 writeln!(
320 w,
321 "\nThis is an Abstract Base Class, you cannot instantiate it."
322 )?;
323 }
324
325 if !properties.is_empty() {
326 writeln!(w, "\n## Properties")?;
327 document_type_properties(env, w, info, properties, None)?;
328
329 for parent_info in &info.supertypes {
330 match env.library.type_(parent_info.type_id) {
331 Type::Class(cl) => {
332 if !cl.properties.is_empty() {
333 document_type_properties(env, w, info, &cl.properties, Some(&cl.name))?;
334 }
335 }
336 Type::Interface(iface) => {
337 if !iface.properties.is_empty() {
338 document_type_properties(
339 env,
340 w,
341 info,
342 &iface.properties,
343 Some(&iface.name),
344 )?;
345 }
346 }
347 _ => (),
348 }
349 }
350 }
351 if !signals.is_empty() {
352 writeln!(w, "\n## Signals")?;
353 document_type_signals(env, w, info, signals, None)?;
354
355 for parent_info in &info.supertypes {
356 match env.library.type_(parent_info.type_id) {
357 Type::Class(cl) => {
358 if !cl.signals.is_empty() {
359 document_type_signals(env, w, info, &cl.signals, Some(&cl.name))?;
360 }
361 }
362 Type::Interface(iface) => {
363 if !iface.signals.is_empty() {
364 document_type_signals(env, w, info, &iface.signals, Some(&iface.name))?;
365 }
366 }
367 _ => (),
368 }
369 }
370 }
371
372 let impl_self = if has_trait { Some(info.type_id) } else { None };
373 let mut implements = impl_self
374 .iter()
375 .chain(env.class_hierarchy.supertypes(info.type_id))
376 .filter(|&tid| {
377 !env.type_status(&tid.full_name(&env.library)).ignored()
378 && !env.type_(*tid).is_final_type()
379 && !env.type_(*tid).is_fundamental()
380 })
381 .map(|&tid| get_type_trait_for_implements(env, tid))
382 .collect::<Vec<_>>();
383 implements.extend(manual_traits);
384
385 if !implements.is_empty() {
386 writeln!(w, "\n# Implements\n")?;
387 writeln!(w, "{}", &implements.join(", "))?;
388 }
389 Ok(())
390 })?;
391
392 if has_builder {
393 let builder_ty = TypeStruct::new(SType::Impl, &format!("{}Builder", info.name));
394 let mut builder_properties: Vec<_> = properties.iter().collect();
395 for parent_info in &info.supertypes {
396 match env.library.type_(parent_info.type_id) {
397 Type::Class(cl) => {
398 builder_properties.extend(cl.properties.iter().filter(|p| p.writable));
399 }
400 Type::Interface(iface) => {
401 builder_properties.extend(iface.properties.iter().filter(|p| p.writable));
402 }
403 _ => (),
404 }
405 }
406 for property in &builder_properties {
407 if !property.writable {
408 continue;
409 }
410 let ty = TypeStruct {
411 ty: SType::Fn,
412 name: nameutil::signal_to_snake(&property.name),
413 parent: Some(Box::new(builder_ty.clone())),
414 args: vec![],
415 };
416 write_item_doc(w, &ty, |w| {
417 if let Some(ref doc) = property.doc {
418 writeln!(
419 w,
420 "{}",
421 reformat_doc(
422 &fix_param_names(doc, &None),
423 env,
424 Some((&info.type_id, Some(LocationInObject::Builder)))
425 )
426 )?;
427 }
428 if let Some(ref doc) = property.doc_deprecated {
429 writeln!(
430 w,
431 "{}",
432 reformat_doc(
433 &fix_param_names(doc, &None),
434 env,
435 Some((&info.type_id, Some(LocationInObject::Builder)))
436 )
437 )?;
438 }
439 Ok(())
440 })?;
441 }
442 }
443
444 if has_trait {
445 write_item_doc(w, &ty_ext, |w| {
446 writeln!(w, "Trait containing all [`struct@{}`] methods.", ty.name)?;
447
448 let mut implementors = std::iter::once(info.type_id)
449 .chain(env.class_hierarchy.subtypes(info.type_id))
450 .filter(|&tid| !env.type_status(&tid.full_name(&env.library)).ignored())
451 .map(|tid| {
452 format!(
453 "[`{0}`][struct@crate::{0}]",
454 env.library.type_(tid).get_name()
455 )
456 })
457 .collect::<Vec<_>>();
458 implementors.sort();
459
460 writeln!(w, "\n# Implementors\n")?;
461 writeln!(w, "{}", implementors.join(", "))?;
462 Ok(())
463 })?;
464 }
465
466 let ty = TypeStruct {
467 ty: SType::Impl,
468 ..ty
469 };
470
471 for function in functions {
472 let configured_functions = obj.functions.matched(&function.name);
473 let is_manual = configured_functions.iter().any(|f| f.status.manual());
474 let (ty, object_location) = if (has_trait || is_manual)
475 && function.parameters.iter().any(|p| p.instance_parameter)
476 && !info.final_type
477 {
478 if let Some(struct_name) = configured_functions
479 .iter()
480 .find_map(|f| f.doc_struct_name.as_ref())
481 {
482 (
483 TypeStruct::new(SType::Impl, struct_name),
484 Some(LocationInObject::Impl),
485 )
486 }
487 else if let Some(trait_name) = configured_functions
490 .iter()
491 .find_map(|f| f.doc_trait_name.as_ref())
492 {
493 (
494 TypeStruct::new(SType::Trait, trait_name),
495 None,
500 )
501 } else if is_manual {
502 (
503 TypeStruct::new(SType::Trait, &format!("{}ExtManual", info.name)),
504 Some(LocationInObject::ExtManual),
505 )
506 } else {
507 (ty_ext.clone(), Some(LocationInObject::Ext))
508 }
509 } else {
510 (ty.clone(), Some(LocationInObject::Impl))
511 };
512 if let Some(c_identifier) = &function.c_identifier {
513 let f_info = info.functions.iter().find(|f| &f.glib_name == c_identifier);
514 let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env));
515
516 if !should_be_documented {
517 continue;
518 }
519
520 let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone());
522 let doc_ignored_parameters = f_info
523 .map(|analyzed_f| analyzed_f.doc_ignore_parameters.clone())
524 .unwrap_or_default();
525 create_fn_doc(
526 w,
527 env,
528 function,
529 Some(Box::new(ty)),
530 fn_new_name,
531 &doc_ignored_parameters,
532 Some((&info.type_id, object_location)),
533 f_info.is_none_or(|f| f.generate_doc),
534 )?;
535 }
536 }
537 for signal in signals {
538 let configured_signals = obj.signals.matched(&signal.name);
539 let (ty, object_location) = if has_trait {
540 if let Some(trait_name) = configured_signals
541 .iter()
542 .find_map(|f| f.doc_trait_name.as_ref())
543 {
544 (TypeStruct::new(SType::Trait, trait_name), None)
545 } else {
546 (ty_ext.clone(), Some(LocationInObject::Ext))
547 }
548 } else {
549 (ty.clone(), Some(LocationInObject::Impl))
550 };
551 create_fn_doc(
552 w,
553 env,
554 signal,
555 Some(Box::new(ty)),
556 None,
557 &HashSet::new(),
558 Some((&info.type_id, object_location)),
559 configured_signals.iter().all(|s| s.generate_doc),
560 )?;
561 }
562
563 for function in virtual_methods {
564 let configured_virtual_methods = obj.virtual_methods.matched(&function.name);
565 let (ty, object_location) = if let Some(trait_name) = configured_virtual_methods
566 .iter()
567 .find_map(|f| f.doc_trait_name.as_ref())
568 {
569 (
570 TypeStruct::new(SType::Trait, trait_name),
571 None,
576 )
577 } else {
578 (
579 TypeStruct::new(SType::Trait, &format!("{}Impl", info.name)),
580 Some(LocationInObject::VirtualExt),
581 )
582 };
583
584 if let Some(c_identifier) = &function.c_identifier {
585 let f_info: Option<&analysis::functions::Info> = info
586 .virtual_methods
587 .iter()
588 .find(|f| &f.glib_name == c_identifier);
589 let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env));
590 if !should_be_documented {
591 continue;
592 }
593
594 let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone());
596 let doc_ignored_parameters = f_info
597 .map(|analyzed_f| analyzed_f.doc_ignore_parameters.clone())
598 .unwrap_or_default();
599 create_fn_doc(
600 w,
601 env,
602 function,
603 Some(Box::new(ty)),
604 fn_new_name,
605 &doc_ignored_parameters,
606 Some((&info.type_id, object_location)),
607 f_info.is_none_or(|f| f.generate_doc),
608 )?;
609 }
610 }
611
612 for property in properties {
613 let getter_name = if property.getter.is_some() {
614 None } else {
616 info.properties
617 .iter()
618 .filter(|p| p.is_get)
619 .find(|p| p.name == property.name)
620 .map(|p| p.func_name.clone())
621 };
622 let setter_name = if property.setter.is_some() {
623 None } else {
625 info.properties
626 .iter()
627 .filter(|p| !p.is_get)
628 .find(|p| p.name == property.name)
629 .map(|p| p.func_name.clone())
630 };
631
632 let (ty, object_location) = if has_trait {
633 let configured_properties = obj.properties.matched(&property.name);
634 if let Some(trait_name) = configured_properties
635 .iter()
636 .find_map(|f| f.doc_trait_name.as_ref())
637 {
638 (TypeStruct::new(SType::Trait, trait_name), None)
639 } else {
640 (ty_ext.clone(), Some(LocationInObject::Ext))
641 }
642 } else {
643 (ty.clone(), Some(LocationInObject::Impl))
644 };
645 create_property_doc(
646 w,
647 env,
648 property,
649 Some(Box::new(ty)),
650 (&info.type_id, object_location),
651 getter_name,
652 setter_name,
653 info,
654 )?;
655 }
656 Ok(())
657}
658
659fn create_record_doc(w: &mut dyn Write, env: &Env, info: &analysis::record::Info) -> Result<()> {
660 let record: &Record = env.library.type_(info.type_id).to_ref_as();
661 let ty = record.to_stripper_type();
662 let object = env.config.objects.get(&info.full_name);
663 let trait_name = object
664 .and_then(|o| o.trait_name.clone())
665 .unwrap_or_else(|| format!("{}Ext", info.name));
666 let generate_doc = object.is_none_or(|r| r.generate_doc);
667 if generate_doc {
668 write_item_doc(w, &ty, |w| {
669 if let Some(ref doc) = record.doc {
670 writeln!(w, "{}", reformat_doc(doc, env, Some((&info.type_id, None))))?;
671 }
672 if let Some(ver) = info.deprecated_version {
673 writeln!(w, "\n# Deprecated since {ver}\n")?;
674 } else if record.doc_deprecated.is_some() {
675 writeln!(w, "\n# Deprecated\n")?;
676 }
677 if let Some(ref doc) = record.doc_deprecated {
678 writeln!(w, "{}", reformat_doc(doc, env, Some((&info.type_id, None))))?;
679 }
680 Ok(())
681 })?;
682 }
683
684 for function in &record.functions {
685 let function_ty = if function.kind == FunctionKind::ClassMethod {
686 TypeStruct::new(SType::Trait, &trait_name)
687 } else {
688 TypeStruct {
689 ty: SType::Impl,
690 parent: ty.parent.clone(),
691 name: ty.name.clone(),
692 args: ty.args.clone(),
693 }
694 };
695 if let Some(c_identifier) = &function.c_identifier {
696 let f_info = info.functions.iter().find(|f| &f.glib_name == c_identifier);
697 let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env));
698 if !should_be_documented {
699 continue;
700 }
701 let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone());
702
703 create_fn_doc(
704 w,
705 env,
706 function,
707 Some(Box::new(function_ty)),
708 fn_new_name,
709 &HashSet::new(),
710 Some((&info.type_id, None)),
711 f_info.is_none_or(|f| f.generate_doc),
712 )?;
713 }
714 }
715 Ok(())
716}
717
718fn create_enum_doc(w: &mut dyn Write, env: &Env, enum_: &Enumeration, tid: TypeId) -> Result<()> {
719 let ty = enum_.to_stripper_type();
720 let config = env.config.objects.get(&tid.full_name(&env.library));
721
722 if config.is_none_or(|c| c.generate_doc) {
723 write_item_doc(w, &ty, |w| {
724 if let Some(ref doc) = enum_.doc {
725 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
726 }
727 if let Some(ver) = enum_.deprecated_version {
728 writeln!(w, "\n# Deprecated since {ver}\n")?;
729 } else if enum_.doc_deprecated.is_some() {
730 writeln!(w, "\n# Deprecated\n")?;
731 }
732 if let Some(ref doc) = enum_.doc_deprecated {
733 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
734 }
735 Ok(())
736 })?;
737 }
738
739 for member in &enum_.members {
740 let generate_doc = config
741 .and_then(|m| {
742 m.members
743 .matched(&member.name)
744 .first()
745 .map(|m| m.generate_doc && !m.status.ignored())
746 })
747 .unwrap_or(true);
748
749 if generate_doc && member.doc.is_some() {
750 let sub_ty = TypeStruct {
751 name: nameutil::enum_member_name(&member.name),
752 parent: Some(Box::new(ty.clone())),
753 ty: SType::Variant,
754 args: Vec::new(),
755 };
756 write_item_doc(w, &sub_ty, |w| {
757 if let Some(ref doc) = member.doc {
758 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
759 }
760 if let Some(ref doc) = member.doc_deprecated {
761 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
762 }
763 Ok(())
764 })?;
765 }
766 }
767
768 Ok(())
769}
770
771fn create_bitfield_doc(
772 w: &mut dyn Write,
773 env: &Env,
774 bitfield: &Bitfield,
775 tid: TypeId,
776) -> Result<()> {
777 let ty = bitfield.to_stripper_type();
778 let config = env.config.objects.get(&tid.full_name(&env.library));
779
780 write_item_doc(w, &ty, |w| {
781 if config.is_none_or(|c| c.generate_doc) {
782 if let Some(ref doc) = bitfield.doc {
783 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
784 }
785 }
786 if let Some(ver) = bitfield.deprecated_version {
787 writeln!(w, "\n# Deprecated since {ver}\n")?;
788 } else if bitfield.doc_deprecated.is_some() {
789 writeln!(w, "\n# Deprecated\n")?;
790 }
791 if let Some(ref doc) = bitfield.doc_deprecated {
792 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
793 }
794 Ok(())
795 })?;
796
797 for member in &bitfield.members {
798 let generate_doc = config
799 .and_then(|m| {
800 m.members
801 .matched(&member.name)
802 .first()
803 .map(|m| m.generate_doc)
804 })
805 .unwrap_or(true);
806
807 if generate_doc && member.doc.is_some() {
808 let sub_ty = TypeStruct {
809 name: nameutil::bitfield_member_name(&member.name),
810 parent: Some(Box::new(ty.clone())),
811 ty: SType::Const,
812 args: Vec::new(),
813 };
814 write_item_doc(w, &sub_ty, |w| {
815 if let Some(ref doc) = member.doc {
816 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
817 }
818 if let Some(ref doc) = member.doc_deprecated {
819 writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?;
820 }
821 Ok(())
822 })?;
823 }
824 }
825
826 Ok(())
827}
828
829fn param_name() -> &'static Regex {
830 static REGEX: OnceLock<Regex> = OnceLock::new();
831 REGEX.get_or_init(|| Regex::new(r"@(\w+)\b").unwrap())
832}
833
834fn fix_param_names<'a>(doc: &'a str, self_name: &Option<String>) -> Cow<'a, str> {
835 param_name().replace_all(doc, |caps: &Captures<'_>| {
836 if let Some(self_name) = self_name {
837 if &caps[1] == self_name {
838 return "@self".into();
839 }
840 }
841 format!("@{}", nameutil::mangle_keywords(&caps[1]))
842 })
843}
844
845fn create_fn_doc<T>(
846 w: &mut dyn Write,
847 env: &Env,
848 fn_: &T,
849 parent: Option<Box<TypeStruct>>,
850 name_override: Option<String>,
851 doc_ignored_parameters: &HashSet<String>,
852 in_type: Option<(&TypeId, Option<LocationInObject>)>,
853 generate_doc: bool,
854) -> Result<()>
855where
856 T: FunctionLikeType + ToStripperType,
857{
858 if !generate_doc {
859 return Ok(());
860 }
861 if env.is_totally_deprecated(None, *fn_.deprecated_version()) {
862 return Ok(());
863 }
864 if fn_.doc().is_none()
865 && fn_.doc_deprecated().is_none()
866 && fn_.ret().doc.is_none()
867 && fn_.parameters().iter().all(|p| p.doc.is_none())
868 {
869 return Ok(());
870 }
871
872 let mut st = fn_.to_stripper_type();
873 if let Some(name_override) = name_override {
874 st.name = nameutil::mangle_keywords(name_override).to_string();
875 }
876 let ty = TypeStruct { parent, ..st };
877 let self_name: Option<String> = fn_
878 .parameters()
879 .iter()
880 .find(|p| p.instance_parameter)
881 .map(|p| p.name.clone());
882
883 write_item_doc(w, &ty, |w| {
884 if let Some(doc) = fn_.doc() {
885 writeln!(
886 w,
887 "{}",
888 reformat_doc(&fix_param_names(doc, &self_name), env, in_type)
889 )?;
890 }
891 if let Some(ver) = fn_.deprecated_version() {
892 writeln!(w, "\n# Deprecated since {ver}\n")?;
893 } else if fn_.doc_deprecated().is_some() {
894 writeln!(w, "\n# Deprecated\n")?;
895 }
896 if let Some(doc) = fn_.doc_deprecated() {
897 writeln!(
898 w,
899 "{}",
900 reformat_doc(&fix_param_names(doc, &self_name), env, in_type)
901 )?;
902 }
903
904 let mut indices_to_ignore: BTreeSet<_> = fn_
906 .parameters()
907 .iter()
908 .filter_map(|param| param.array_length)
909 .collect();
910 if let Some(indice) = fn_.ret().array_length {
911 indices_to_ignore.insert(indice);
912 }
913
914 let no_array_length_params: Vec<_> = fn_
916 .parameters()
917 .iter()
918 .enumerate()
919 .filter_map(|(indice, param)| {
920 (!indices_to_ignore.contains(&(indice as u32))).then_some(param)
921 })
922 .filter(|param| !param.instance_parameter)
923 .collect();
924
925 let in_parameters = no_array_length_params.iter().filter(|param| {
926 let ignore = IGNORED_C_FN_PARAMS.contains(¶m.name.as_str())
927 || doc_ignored_parameters.contains(¶m.name)
928 || param.direction == ParameterDirection::Out
929 || (param.name == "error" && param.c_type == "GError**")
931 || (param.name == "data" && param.c_type == "gpointer");
933 !ignore
934 });
935
936 for parameter in in_parameters {
937 if parameter.name.is_empty() {
938 continue;
939 }
940 if let Some(ref doc) = parameter.doc {
941 writeln!(
942 w,
943 "## `{}`",
944 nameutil::mangle_keywords(parameter.name.as_str())
945 )?;
946 writeln!(
947 w,
948 "{}",
949 reformat_doc(&fix_param_names(doc, &self_name), env, in_type)
950 )?;
951 }
952 }
953
954 let out_parameters: Vec<_> = no_array_length_params
955 .iter()
956 .filter(|param| {
957 param.direction == ParameterDirection::Out
958 && !doc_ignored_parameters.contains(¶m.name)
959 && !(param.name == "error" && param.c_type == "GError**")
960 })
961 .collect();
962
963 if fn_.ret().doc.is_some() || !out_parameters.is_empty() {
964 writeln!(w, "\n# Returns\n")?;
965 }
966 if let Some(ref doc) = fn_.ret().doc {
968 writeln!(
969 w,
970 "{}",
971 reformat_doc(&fix_param_names(doc, &self_name), env, in_type)
972 )?;
973 }
974 for parameter in out_parameters {
976 if let Some(ref doc) = parameter.doc {
977 writeln!(
978 w,
979 "\n## `{}`",
980 nameutil::mangle_keywords(parameter.name.as_str())
981 )?;
982 writeln!(
983 w,
984 "{}",
985 reformat_doc(&fix_param_names(doc, &self_name), env, in_type),
986 )?;
987 }
988 }
989 Ok(())
990 })
991}
992
993fn create_property_doc(
994 w: &mut dyn Write,
995 env: &Env,
996 property: &Property,
997 parent: Option<Box<TypeStruct>>,
998 in_type: (&TypeId, Option<LocationInObject>),
999 getter_name: Option<String>,
1000 setter_name: Option<String>,
1001 obj_info: &analysis::object::Info,
1002) -> Result<()> {
1003 if env.is_totally_deprecated(Some(in_type.0.ns_id), property.deprecated_version) {
1004 return Ok(());
1005 }
1006 let generate_doc = env
1007 .config
1008 .objects
1009 .get(&obj_info.type_id.full_name(&env.library))
1010 .is_none_or(|r| r.generate_doc);
1011 if !generate_doc {
1012 return Ok(());
1013 }
1014 if property.doc.is_none()
1015 && property.doc_deprecated.is_none()
1016 && (property.readable || property.writable)
1017 {
1018 return Ok(());
1019 }
1020 let mut v = Vec::with_capacity(2);
1021
1022 if let Some(getter_name) = getter_name {
1023 v.push(TypeStruct {
1024 parent: parent.clone(),
1025 ..TypeStruct::new(SType::Fn, &getter_name)
1026 });
1027 }
1028 if let Some(setter_name) = setter_name {
1029 v.push(TypeStruct {
1030 parent,
1031 ..TypeStruct::new(SType::Fn, &setter_name)
1032 });
1033 }
1034
1035 for item in &v {
1036 write_item_doc(w, item, |w| {
1037 if let Some(ref doc) = property.doc {
1038 writeln!(
1039 w,
1040 "{}",
1041 reformat_doc(&fix_param_names(doc, &None), env, Some(in_type))
1042 )?;
1043 }
1044 if let Some(ver) = property.deprecated_version {
1045 writeln!(w, "\n# Deprecated since {ver}\n")?;
1046 } else if property.doc_deprecated.is_some() {
1047 writeln!(w, "\n# Deprecated\n")?;
1048 }
1049 if let Some(ref doc) = property.doc_deprecated {
1050 writeln!(
1051 w,
1052 "{}",
1053 reformat_doc(&fix_param_names(doc, &None), env, Some(in_type))
1054 )?;
1055 }
1056 Ok(())
1057 })?;
1058 }
1059 Ok(())
1060}
1061
1062fn get_type_trait_for_implements(env: &Env, tid: TypeId) -> String {
1063 let trait_name = if let Some(&GObject {
1064 trait_name: Some(ref trait_name),
1065 ..
1066 }) = env.config.objects.get(&tid.full_name(&env.library))
1067 {
1068 trait_name.clone()
1069 } else {
1070 format!("{}Ext", env.library.type_(tid).get_name())
1071 };
1072 if tid.ns_id == MAIN_NAMESPACE {
1073 format!("[`{trait_name}`][trait@crate::prelude::{trait_name}]")
1074 } else if let Some(symbol) = env.symbols.borrow().by_tid(tid) {
1075 let mut symbol = symbol.clone();
1076 symbol.make_trait(&trait_name);
1077 format!("[`trait@{}`]", &symbol.full_rust_name())
1078 } else {
1079 error!("Type {} doesn't have crate", tid.full_name(&env.library));
1080 format!("`{trait_name}`")
1081 }
1082}
1083
1084pub fn get_type_manual_traits_for_implements(
1085 env: &Env,
1086 info: &analysis::object::Info,
1087) -> Vec<String> {
1088 let mut manual_trait_iters = Vec::new();
1089 for type_id in [info.type_id]
1090 .iter()
1091 .chain(info.supertypes.iter().map(|stid| &stid.type_id))
1092 {
1093 let full_name = type_id.full_name(&env.library);
1094 if let Some(obj) = &env.config.objects.get(&full_name) {
1095 if !obj.manual_traits.is_empty() {
1096 manual_trait_iters.push(obj.manual_traits.iter());
1097 }
1098 }
1099 }
1100
1101 manual_trait_iters
1102 .into_iter()
1103 .flatten()
1104 .map(|name| format!("[`{name}`][trait@crate::prelude::{name}]"))
1105 .collect()
1106}
1107
1108pub fn document_type_properties(
1109 env: &Env,
1110 w: &mut dyn Write,
1111 info: &analysis::object::Info,
1112 properties: &[Property],
1113 subtype: Option<&str>,
1114) -> Result<()> {
1115 if let Some(subtype_name) = subtype {
1116 writeln!(w, "<details><summary><h4>{subtype_name}</h4></summary>")?;
1117 }
1118 for property in properties {
1119 let mut details = Vec::new();
1120 if property.readable {
1121 details.push("Readable");
1122 }
1123 if property.writable {
1124 details.push("Writeable");
1125 }
1126 if property.construct {
1127 details.push("Construct");
1128 }
1129 if property.construct_only {
1130 details.push("Construct Only");
1131 }
1132 if let Some(doc) = &property.doc {
1133 writeln!(
1134 w,
1135 "\n\n#### `{}`\n {}\n\n{}",
1136 property.name,
1137 reformat_doc(
1138 &fix_param_names(doc, &None),
1139 env,
1140 Some((&info.type_id, None))
1141 ),
1142 details.join(" | "),
1143 )?;
1144 } else {
1145 writeln!(w, "\n\n#### `{}`\n {}", property.name, details.join(" | "),)?;
1146 }
1147 }
1148 if subtype.is_some() {
1149 writeln!(w, "</details>")?;
1150 }
1151 Ok(())
1152}
1153
1154pub fn document_type_signals(
1155 env: &Env,
1156 w: &mut dyn Write,
1157 info: &analysis::object::Info,
1158 signals: &[Signal],
1159 subtype: Option<&str>,
1160) -> Result<()> {
1161 if let Some(subtype_name) = subtype {
1162 writeln!(w, "<details><summary><h4>{subtype_name}</h4></summary>")?;
1163 }
1164 for signal in signals {
1165 let mut details = Vec::new();
1166 if signal.is_action {
1167 details.push("Action");
1168 }
1169 if signal.is_detailed {
1170 details.push("Detailed");
1171 }
1172 if let Some(doc) = &signal.doc {
1173 writeln!(
1174 w,
1175 "\n\n#### `{}`\n {}\n\n{}",
1176 signal.name,
1177 reformat_doc(
1178 &fix_param_names(doc, &None),
1179 env,
1180 Some((&info.type_id, None))
1181 ),
1182 details.join(" | "),
1183 )?;
1184 } else {
1185 writeln!(w, "\n\n#### `{}`\n {}", signal.name, details.join(" | "),)?;
1186 }
1187 }
1188 if subtype.is_some() {
1189 writeln!(w, "</details>")?;
1190 }
1191 Ok(())
1192}