libgir/codegen/doc/
mod.rs

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
27// A list of C parameters that are not used directly by the Rust bindings
28const 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        // strings are mapped to a static
219        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            // We use "original_name" here to be sure to get the correct object since the "name"
488            // field could have been renamed.
489            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                    // Because we cannot sensibly deduce where the docs end up,
496                    // assume they're outside the docs so that no `Self::` links
497                    // are generated.  It is currently quite uncommon to specify
498                    // the `{}Manual` trait, which would be ObjectLocation::ExtManual.
499                    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            // Retrieve the new_name computed during analysis, if any
521            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                // Because we cannot sensibly deduce where the docs end up,
572                // assume they're outside the docs so that no `Self::` links
573                // are generated.  It is currently quite uncommon to specify
574                // the `{}Manual` trait, which would be ObjectLocation::ExtManual.
575                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            // Retrieve the new_name computed during analysis, if any
595            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 // Don't generate a getter for the property, there is a getter
615        } 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 // don't generate a setter for the property, there is a setter
624        } 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        // A list of parameter positions to filter out
905        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        // The original list of parameters without the ones that specify an array length
915        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(&param.name.as_str())
927                || doc_ignored_parameters.contains(&param.name)
928                || param.direction == ParameterDirection::Out
929                // special case error pointer as it's transformed to a Result
930                || (param.name == "error" && param.c_type == "GError**")
931                // special case `data` with explicit `gpointer` type as it could be something else (unlike `user_data`)
932                || (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(&param.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        // document function's return
967        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        // document OUT parameters as part of the function's Return
975        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}