Skip to content

Commit

Permalink
godot-rust#810 Docs comments do not show up if only some of the class…
Browse files Browse the repository at this point in the history
… members/methods are documented

- Extend docs test case to feature some undocumented members of godot instances
- Change map&unwrap in docs.rs to filter_map (allow to document only subset of all the properties of a given class)
- Generate documentation blocks for signals & constants on compile-time
  • Loading branch information
Yarwin committed Jul 27, 2024
1 parent 355dae9 commit 1ba8157
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 57 deletions.
27 changes: 14 additions & 13 deletions godot-core/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,29 @@ pub struct StructDocs {
/// ```
#[derive(Clone, Copy, Debug, Default)]
pub struct InherentImplDocs {
pub methods: &'static str,
pub signals: &'static str,
pub constants: &'static str,
pub methods: Option<&'static str>,
pub signals_block: &'static str,
pub constants_block: &'static str,
}

#[derive(Default)]
struct DocPieces {
definition: StructDocs,
inherent: InherentImplDocs,
virtual_methods: &'static str,
virtual_methods: Option<&'static str>,
}

#[doc(hidden)]
/// This function scours the registered plugins to find their documentation pieces,
/// and strings them together.
///
/// It returns an iterator over XML documents.
/// Returns an iterator over XML documents.
pub fn gather_xml_docs() -> impl Iterator<Item = String> {
let mut map = HashMap::<&'static str, DocPieces>::new();
crate::private::iterate_plugins(|x| match x.item {
PluginItem::InherentImpl {
docs: Some(docs), ..
} => map.entry(x.class_name.as_str()).or_default().inherent = docs,
PluginItem::InherentImpl { docs, .. } => {
map.entry(x.class_name.as_str()).or_default().inherent = docs
}
PluginItem::ITraitImpl {
virtual_method_docs,
..
Expand All @@ -79,20 +79,21 @@ pub fn gather_xml_docs() -> impl Iterator<Item = String> {

let InherentImplDocs {
methods,
signals,
constants,
signals_block,
constants_block,
} = pieces.inherent;

let virtual_methods = pieces.virtual_methods;
let brief = description.split_once("[br]").map(|(x, _)| x).unwrap_or_default();
let methods_block: String = if methods.is_some() || virtual_methods.is_some() { format!("<methods>{m}{vm}</methods>", m=methods.unwrap_or_default(), vm=virtual_methods.unwrap_or_default()) } else { String::new()};
format!(r#"
<?xml version="1.0" encoding="UTF-8"?>
<class name="{class}" inherits="{base}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>{brief}</brief_description>
<description>{description}</description>
<methods>{methods}{virtual_methods}</methods>
<constants>{constants}</constants>
<signals>{signals}</signals>
{methods_block}
{constants_block}
{signals_block}
<members>{members}</members>
</class>"#)
},
Expand Down
4 changes: 2 additions & 2 deletions godot-core/src/registry/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ pub enum PluginItem {
/// Always present since that's the entire point of this `impl` block.
register_methods_constants_fn: ErasedRegisterFn,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: Option<InherentImplDocs>,
docs: InherentImplDocs,
},

/// Collected from `#[godot_api] impl I... for MyClass`.
ITraitImpl {
#[cfg(all(since_api = "4.3", feature = "docs"))]
/// Virtual method documentation.
virtual_method_docs: &'static str,
virtual_method_docs: Option<&'static str>,
/// Callback to user-defined `register_class` function.
user_register_fn: Option<ErasedRegisterFn>,

Expand Down
82 changes: 51 additions & 31 deletions godot-macros/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub fn make_definition_docs(
let members = members
.into_iter()
.filter(|x| x.var.is_some() | x.export.is_some())
.map(member)
.collect::<Option<String>>()?;
.filter_map(member)
.collect::<String>();
Some(quote! {
docs: ::godot::docs::StructDocs {
base: #base,
Expand All @@ -39,52 +39,72 @@ pub fn make_inherent_impl_docs(
constants: &[ConstDefinition],
signals: &[SignalDefinition],
) -> TokenStream {
/// Generates TokenStream containing field definitions for documented methods and documentation blocks for constants and signals.
fn pieces(
functions: &[FuncDefinition],
signals: &[SignalDefinition],
constants: &[ConstDefinition],
) -> Option<TokenStream> {
) -> TokenStream {
let to_tagged = |s: String, tag: &str| -> String {
if s.is_empty() {
s
} else {
format!("<{tag}>{s}</{tag}>")
}
};

let signals_block = to_tagged(
signals
.iter()
.filter_map(make_signal_docs)
.collect::<String>(),
"signals",
);
let constants_block = to_tagged(
constants
.iter()
.map(|ConstDefinition { raw_constant }| raw_constant)
.filter_map(make_constant_docs)
.collect::<String>(),
"constants",
);

let methods = functions
.iter()
.map(make_method_docs)
.collect::<Option<String>>()?;
let signals = signals
.iter()
.map(make_signal_docs)
.collect::<Option<String>>()?;
let constants = constants
.iter()
.map(|ConstDefinition { raw_constant: x }| x)
.map(make_constant_docs)
.collect::<Option<String>>()?;
let field_definition = quote! {
.filter_map(make_method_docs)
.collect::<String>();

let methods = if methods.is_empty() {
quote! { None }
} else {
quote! {#methods.into()}
};

quote! {
docs: ::godot::docs::InherentImplDocs {
methods: #methods,
signals: #signals,
constants: #constants,
}.into()
};
Some(field_definition)
signals_block: #signals_block,
constants_block: #constants_block,
}
}
}
pieces(functions, signals, constants).unwrap_or_else(|| quote! { docs: None })
pieces(functions, signals, constants)
}

pub fn make_virtual_impl_docs(vmethods: &[ImplMember]) -> TokenStream {
match vmethods
let virtual_methods = vmethods
.iter()
.filter_map(|x| match x {
venial::ImplMember::AssocFunction(f) => Some(f.clone()),
_ => None,
})
.map(make_virtual_method_docs)
.collect::<Option<String>>()
{
Some(vmethods) => quote! {
virtual_method_docs: #vmethods,
},
None => quote! {
virtual_method_docs: ""
},
.filter_map(make_virtual_method_docs)
.collect::<String>();

if virtual_methods.is_empty() {
quote! { virtual_method_docs: None, }
} else {
quote! { virtual_method_docs: #virtual_methods.into(), }
}
}

Expand Down
48 changes: 42 additions & 6 deletions godot/tests/docs.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![cfg(feature = "register-docs")]
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
Expand Down Expand Up @@ -75,57 +76,92 @@ use godot::prelude::*;
/// these
#[derive(GodotClass)]
#[class(base=Node)]
pub struct ExtremelyDocumented {
pub struct FairlyDocumented {
#[doc = r#"this is very documented"#]
#[var]
item: f32,
/// is it documented?
#[var]
item_2: i64,
/// this isnt documented
_other_item: (),
/// nor this
base: Base<Node>,
}

#[godot_api]
impl INode for ExtremelyDocumented {
impl INode for FairlyDocumented {
/// initialize this
fn init(base: Base<Node>) -> Self {
Self {
base,
item: 883.0,
item_2: 25,
_other_item: {},
}
}
}

#[godot_api]
impl ExtremelyDocumented {
impl FairlyDocumented {
#[constant]
/// Documentation.
const RANDOM: i64 = 4;

#[constant]
const PURPOSE: i64 = 42;

#[func]
fn totally_undocumented_function(&self) -> i64 {
5
}

#[func]
/// huh
fn ye(&self) -> f32 {
self.item
}

#[func(gd_self, virtual)]
fn virtual_undocumented(_s: Gd<Self>) {
panic!("no implementation")
}

#[func(gd_self, virtual)]
/// some virtual function that should be overridden by a user
///
/// some multiline doc
fn virtual_documented(_s: Gd<Self>) {
panic!("please provide user implementation")
}

#[func]
/// wow
///
/// some multiline doc
fn ne(_x: f32) -> Gd<Self> {
panic!()
}

#[signal]
fn undocumented_signal(p: Vector3, w: f64);

#[signal]
/// some user signal
///
/// some multiline doc
fn undocumented_signal(p: Vector3, w: f64);
}

#[test]
#[cfg(feature = "register-docs")]
fn correct() {
// Uncomment if implementation changes and expected output file should be rewritten.
// std::fs::write(
// "tests/docs.xml",
// "tests/test_data/docs.xml",
// godot_core::docs::gather_xml_docs().next().unwrap(),
// );
assert_eq!(
include_str!("docs.xml"),
include_str!("test_data/docs.xml"),
godot_core::docs::gather_xml_docs().next().unwrap()
);
}
25 changes: 20 additions & 5 deletions godot/tests/docs.xml → godot/tests/test_data/docs.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

<?xml version="1.0" encoding="UTF-8"?>
<class name="ExtremelyDocumented" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="FairlyDocumented" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>[i]documented[/i] ~ [b]documented[/b] ~ [AABB] [url=https://github.com/godot-rust/gdext/pull/748]pr[/url]</brief_description>
<description>[i]documented[/i] ~ [b]documented[/b] ~ [AABB] [url=https://github.com/godot-rust/gdext/pull/748]pr[/url][br][br]a few tests:[br][br]headings:[br][br]Some heading[br][br]lists:[br][br][br][br][br][br]links with back-references:[br][br]Blah blah [br][br][br][br]footnotes:[br][br]We cannot florbinate the glorb[br][br][br][br]task lists:[br][br]We must ensure that we've completed[br][br][br][br]tables:[br][br][br][br]images:[br][br][img]http://url/a.png[/img][br][br]blockquotes:[br][br][br][br]ordered list:[br][br][br][br]Something here < this is technically header syntax[br][br]And here[br][br]smart punctuation[br][br]codeblocks:[br][br][codeblock]#![no_main]
#[link_section=\".text\"]
Expand All @@ -16,11 +16,19 @@ these</description>
</description>
</method>

<method name="virtual_documented">
<return type="()" />

<description>
some virtual function that should be overridden by a user[br][br]some multiline doc
</description>
</method>

<method name="ne">
<return type="Gd < ExtremelyDocumented >" />
<return type="Gd < FairlyDocumented >" />
<param index="0" name="x" type="f32" />
<description>
wow
wow[br][br]some multiline doc
</description>
</method>

Expand All @@ -33,6 +41,13 @@ these</description>
</method>
</methods>
<constants><constant name="RANDOM" value="4">Documentation.</constant></constants>
<signals></signals>
<members><member name="item" type="f32" default="">this is very documented</member></members>
<signals>
<signal name="undocumented_signal">
<param index="0" name="p" type="Vector3" /><param index="1" name="w" type="f64" />
<description>
some user signal[br][br]some multiline doc
</description>
</signal>
</signals>
<members><member name="item" type="f32" default="">this is very documented</member><member name="item_2" type="i64" default="">is it documented?</member></members>
</class>

0 comments on commit 1ba8157

Please sign in to comment.