Skip to main content

ladfile_builder/
lib.rs

1//! Parsing definitions for the LAD (Language Agnostic Decleration) file format.
2pub mod plugin;
3
4use bevy_ecs::{
5    reflect::{ReflectComponent, ReflectResource},
6    world::World,
7};
8use bevy_log::warn;
9use bevy_mod_scripting_bindings::{
10    MarkAsCore, MarkAsGenerated, MarkAsSignificant, ReflectReference, ScriptValue,
11    docgen::{
12        TypedThrough,
13        info::FunctionInfo,
14        typed_through::{ThroughTypeInfo, TypedWrapperKind, UntypedWrapperKind},
15    },
16    function::{
17        namespace::Namespace,
18        script_function::{DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext},
19    },
20    into_through_type_info,
21};
22pub use bevy_mod_scripting_bindings_domain::*; // re-export the thing we use
23use bevy_platform::collections::{HashMap, HashSet};
24use bevy_reflect::{NamedField, TypeInfo, TypeRegistry, Typed, UnnamedField};
25use ladfile::*;
26use std::{
27    any::TypeId,
28    borrow::Cow,
29    cmp::{max, min},
30    ffi::OsString,
31    path::PathBuf,
32};
33
34/// We can assume that the types here will be either primitives
35/// or reflect types, as the rest will be covered by typed wrappers
36/// so just check
37fn primitive_from_type_id(type_id: TypeId) -> Option<ReflectionPrimitiveKind> {
38    Some(if type_id == TypeId::of::<bool>() {
39        ReflectionPrimitiveKind::Bool
40    } else if type_id == TypeId::of::<isize>() {
41        ReflectionPrimitiveKind::Isize
42    } else if type_id == TypeId::of::<i8>() {
43        ReflectionPrimitiveKind::I8
44    } else if type_id == TypeId::of::<i16>() {
45        ReflectionPrimitiveKind::I16
46    } else if type_id == TypeId::of::<i32>() {
47        ReflectionPrimitiveKind::I32
48    } else if type_id == TypeId::of::<i64>() {
49        ReflectionPrimitiveKind::I64
50    } else if type_id == TypeId::of::<i128>() {
51        ReflectionPrimitiveKind::I128
52    } else if type_id == TypeId::of::<usize>() {
53        ReflectionPrimitiveKind::Usize
54    } else if type_id == TypeId::of::<u8>() {
55        ReflectionPrimitiveKind::U8
56    } else if type_id == TypeId::of::<u16>() {
57        ReflectionPrimitiveKind::U16
58    } else if type_id == TypeId::of::<u32>() {
59        ReflectionPrimitiveKind::U32
60    } else if type_id == TypeId::of::<u64>() {
61        ReflectionPrimitiveKind::U64
62    } else if type_id == TypeId::of::<u128>() {
63        ReflectionPrimitiveKind::U128
64    } else if type_id == TypeId::of::<f32>() {
65        ReflectionPrimitiveKind::F32
66    } else if type_id == TypeId::of::<f64>() {
67        ReflectionPrimitiveKind::F64
68    } else if type_id == TypeId::of::<char>() {
69        ReflectionPrimitiveKind::Char
70    } else if type_id == TypeId::of::<&'static str>() {
71        ReflectionPrimitiveKind::Str
72    } else if type_id == TypeId::of::<String>() {
73        ReflectionPrimitiveKind::String
74    } else if type_id == TypeId::of::<OsString>() {
75        ReflectionPrimitiveKind::OsString
76    } else if type_id == TypeId::of::<PathBuf>() {
77        ReflectionPrimitiveKind::PathBuf
78    } else if type_id == TypeId::of::<FunctionCallContext>() {
79        ReflectionPrimitiveKind::FunctionCallContext
80    } else if type_id == TypeId::of::<DynamicScriptFunction>() {
81        ReflectionPrimitiveKind::DynamicFunction
82    } else if type_id == TypeId::of::<DynamicScriptFunctionMut>() {
83        ReflectionPrimitiveKind::DynamicFunctionMut
84    } else if type_id == TypeId::of::<ReflectReference>() {
85        ReflectionPrimitiveKind::ReflectReference
86    } else {
87        return None;
88    })
89}
90
91fn type_id_from_primitive(kind: &ReflectionPrimitiveKind) -> Option<TypeId> {
92    Some(match kind {
93        ReflectionPrimitiveKind::Bool => TypeId::of::<bool>(),
94        ReflectionPrimitiveKind::Isize => TypeId::of::<isize>(),
95        ReflectionPrimitiveKind::I8 => TypeId::of::<i8>(),
96        ReflectionPrimitiveKind::I16 => TypeId::of::<i16>(),
97        ReflectionPrimitiveKind::I32 => TypeId::of::<i32>(),
98        ReflectionPrimitiveKind::I64 => TypeId::of::<i64>(),
99        ReflectionPrimitiveKind::I128 => TypeId::of::<i128>(),
100        ReflectionPrimitiveKind::Usize => TypeId::of::<usize>(),
101        ReflectionPrimitiveKind::U8 => TypeId::of::<u8>(),
102        ReflectionPrimitiveKind::U16 => TypeId::of::<u16>(),
103        ReflectionPrimitiveKind::U32 => TypeId::of::<u32>(),
104        ReflectionPrimitiveKind::U64 => TypeId::of::<u64>(),
105        ReflectionPrimitiveKind::U128 => TypeId::of::<u128>(),
106        ReflectionPrimitiveKind::F32 => TypeId::of::<f32>(),
107        ReflectionPrimitiveKind::F64 => TypeId::of::<f64>(),
108        ReflectionPrimitiveKind::Char => TypeId::of::<char>(),
109        ReflectionPrimitiveKind::Str => TypeId::of::<&'static str>(),
110        ReflectionPrimitiveKind::String => TypeId::of::<String>(),
111        ReflectionPrimitiveKind::OsString => TypeId::of::<OsString>(),
112        ReflectionPrimitiveKind::PathBuf => TypeId::of::<PathBuf>(),
113        ReflectionPrimitiveKind::FunctionCallContext => TypeId::of::<FunctionCallContext>(),
114        ReflectionPrimitiveKind::DynamicFunction => TypeId::of::<DynamicScriptFunction>(),
115        ReflectionPrimitiveKind::DynamicFunctionMut => TypeId::of::<DynamicScriptFunctionMut>(),
116        ReflectionPrimitiveKind::ReflectReference => TypeId::of::<ReflectReference>(),
117        ReflectionPrimitiveKind::ScriptValue => TypeId::of::<ScriptValue>(),
118        ReflectionPrimitiveKind::External(_) => return None,
119    })
120}
121
122/// A builder for constructing LAD files.
123/// This should be your preferred way of constructing LAD files.
124pub struct LadFileBuilder<'t> {
125    file: LadFile,
126    type_id_mapping: HashMap<TypeId, LadTypeId>,
127    type_registry: &'t TypeRegistry,
128    sorted: bool,
129    exclude_types_involving_unregistered_types: bool,
130}
131
132impl<'t> LadFileBuilder<'t> {
133    /// Create a new LAD file builder without default primitives.
134    pub fn new_empty(type_registry: &'t TypeRegistry) -> Self {
135        Self {
136            file: LadFile::new(),
137            type_id_mapping: HashMap::new(),
138            type_registry,
139            sorted: false,
140            exclude_types_involving_unregistered_types: false,
141        }
142    }
143
144    /// Create a new LAD file builder loaded with primitives.
145    pub fn new(type_registry: &'t TypeRegistry) -> Self {
146        use ReflectionPrimitiveKind::*;
147        let mut builder = Self::new_empty(type_registry);
148
149        builder
150            .add_bms_primitive(Bool,"A boolean value")
151            .add_bms_primitive(Isize, "A signed pointer-sized integer")
152            .add_bms_primitive(I8, "A signed 8-bit integer")
153            .add_bms_primitive(I16, "A signed 16-bit integer")
154            .add_bms_primitive(I32, "A signed 32-bit integer")
155            .add_bms_primitive(I64, "A signed 64-bit integer")
156            .add_bms_primitive(I128, "A signed 128-bit integer")
157            .add_bms_primitive(Usize, "An unsigned pointer-sized integer")
158            .add_bms_primitive(U8, "An unsigned 8-bit integer")
159            .add_bms_primitive(U16, "An unsigned 16-bit integer")
160            .add_bms_primitive(U32, "An unsigned 32-bit integer")
161            .add_bms_primitive(U64, "An unsigned 64-bit integer")
162            .add_bms_primitive(U128, "An unsigned 128-bit integer")
163            .add_bms_primitive(F32, "A 32-bit floating point number")
164            .add_bms_primitive(F64, "A 64-bit floating point number")
165            .add_bms_primitive(Char, "An 8-bit character")
166            .add_bms_primitive(Str, "A string slice")
167            .add_bms_primitive(String, "A heap allocated string")
168            .add_bms_primitive(OsString, "A heap allocated OS string")
169            .add_bms_primitive(PathBuf, "A heap allocated file path")
170            .add_bms_primitive(FunctionCallContext, "Function call context, if accepted by a function, means the function can access the world in arbitrary ways.")
171            .add_bms_primitive(DynamicFunction, "A callable dynamic function")
172            .add_bms_primitive(DynamicFunctionMut, "A stateful and callable dynamic function")
173            .add_bms_primitive(ScriptValue, "A value representing the union of all representable values")
174            .add_bms_primitive(ReflectReference, "A reference to a reflectable type");
175
176        builder
177    }
178
179    /// Set whether types involving unregistered types should be excluded.
180    /// I.e. `HashMap<T, V>` with T or V not being registered will be excluded.
181    pub fn set_exclude_including_unregistered(&mut self, exclude: bool) -> &mut Self {
182        self.exclude_types_involving_unregistered_types = exclude;
183        self
184    }
185
186    /// Set whether the LAD file should be sorted at build time.
187    pub fn set_sorted(&mut self, sorted: bool) -> &mut Self {
188        self.sorted = sorted;
189        self
190    }
191
192    /// Add a BMS primitive to the LAD file.
193    /// Will do nothing if the type is not a BMS primitive.
194    pub fn add_bms_primitive(
195        &mut self,
196        primitive: ReflectionPrimitiveKind,
197        docs: impl Into<Cow<'static, str>>,
198    ) -> &mut Self {
199        let type_ident = primitive.to_string();
200        let lad_type_id = LadTypeId::new_string_id(type_ident.clone().into());
201        if let Some(type_id) = type_id_from_primitive(&primitive) {
202            self.type_id_mapping.insert(type_id, lad_type_id.clone());
203        }
204        self.file.types.insert(
205            lad_type_id,
206            LadTypeDefinition {
207                identifier: type_ident.clone(),
208                crate_: None,
209                path: type_ident,
210                generics: vec![],
211                documentation: Some(docs.into().to_string()),
212                associated_functions: vec![],
213                layout: LadTypeLayout::Opaque,
214                generated: false,
215                insignificance: default_importance(),
216                metadata: LadTypeMetadata {
217                    is_component: false,
218                    is_resource: false,
219                    is_reflect: false,
220                    mapped_to_primitive_kind: Some(primitive),
221                    misc: Default::default(),
222                },
223            },
224        );
225        self
226    }
227
228    /// Mark a type as generated.
229    pub fn mark_generated(&mut self, type_id: TypeId) -> &mut Self {
230        let type_id = self.lad_id_from_type_id(type_id);
231        if let Some(t) = self.file.types.get_mut(&type_id) {
232            t.generated = true;
233        }
234        self
235    }
236
237    /// Set the insignificance value of a type.
238    pub fn set_insignificance(&mut self, type_id: TypeId, importance: usize) -> &mut Self {
239        let type_id = self.lad_id_from_type_id(type_id);
240        if let Some(t) = self.file.types.get_mut(&type_id) {
241            t.insignificance = importance;
242        }
243        self
244    }
245
246    /// Add a global instance to the LAD file.
247    ///
248    /// Requires the type to be registered via [`Self::add_type`] or [`Self::add_type_info`] first to provide rich type information.
249    ///
250    /// If `is_static` is true, the instance will be treated as a static instance
251    /// and hence not support method call syntax or method calls (i.e. only functions without a self parameter can be called on them).
252    pub fn add_instance<T: 'static + TypedThrough>(
253        &mut self,
254        key: impl Into<Cow<'static, str>>,
255        is_static: bool,
256    ) -> &mut Self {
257        let type_info = T::through_type_info();
258        let type_kind = self.lad_type_kind_from_through_type(&type_info);
259        self.file.globals.insert(
260            key.into(),
261            LadInstance {
262                type_kind,
263                is_static,
264            },
265        );
266        self
267    }
268
269    /// An untyped version of [`Self::add_instance`].
270    ///
271    /// Adds a global instance to the LAD file.
272    pub fn add_instance_dynamic(
273        &mut self,
274        key: impl Into<Cow<'static, str>>,
275        is_static: bool,
276        through_type: ThroughTypeInfo,
277    ) -> &mut Self {
278        let type_kind = self.lad_type_kind_from_through_type(&through_type);
279        self.file.globals.insert(
280            key.into(),
281            LadInstance {
282                type_kind,
283                is_static,
284            },
285        );
286        self
287    }
288
289    /// Adds a global instance to the LAD file with a custom lad type kind.
290    pub fn add_instance_manually(
291        &mut self,
292        key: impl Into<Cow<'static, str>>,
293        is_static: bool,
294        type_kind: LadFieldOrVariableKind,
295    ) -> &mut Self {
296        self.file.globals.insert(
297            key.into(),
298            LadInstance {
299                type_kind,
300                is_static,
301            },
302        );
303        self
304    }
305
306    /// Adds a type which does not implement reflect to the list of types.
307    pub fn add_nonreflect_type<T: 'static>(
308        &mut self,
309        crate_: Option<&str>,
310        documentation: &str,
311    ) -> &mut Self {
312        let path = std::any::type_name::<T>().to_string();
313        let identifier = path
314            .split("::")
315            .last()
316            .map(|o| o.to_owned())
317            .unwrap_or_else(|| path.clone());
318
319        let lad_type_id = self.lad_id_from_type_id(std::any::TypeId::of::<T>());
320        self.file.types.insert(
321            lad_type_id,
322            LadTypeDefinition {
323                identifier,
324                crate_: crate_.map(|s| s.to_owned()),
325                path,
326                generics: vec![],
327                documentation: Some(documentation.trim().to_owned()),
328                associated_functions: vec![],
329                layout: LadTypeLayout::Opaque,
330                generated: false,
331                insignificance: default_importance(),
332                metadata: LadTypeMetadata {
333                    is_component: false,
334                    is_resource: false,
335                    is_reflect: false,
336                    mapped_to_primitive_kind: primitive_from_type_id(std::any::TypeId::of::<T>()),
337                    misc: Default::default(),
338                },
339            },
340        );
341        self
342    }
343
344    /// Add a type definition to the LAD file.
345    ///
346    /// Equivalent to calling [`Self::add_type_info`] with `T::type_info()`.
347    pub fn add_type<T: Typed>(&mut self) -> &mut Self {
348        self.add_type_info(T::type_info());
349        self
350    }
351
352    /// Add a type definition to the LAD file.
353    /// Will overwrite any existing type definitions with the same type id.
354    pub fn add_type_info(&mut self, type_info: &TypeInfo) -> &mut Self {
355        let registration = self.type_registry.get(type_info.type_id());
356
357        let mut insignificance = default_importance();
358        let mut generated = false;
359        let mut is_component = false;
360        let mut is_resource = false;
361        let is_reflect = true;
362        if let Some(registration) = registration {
363            if registration.contains::<MarkAsGenerated>() {
364                generated = true;
365            }
366            if registration.contains::<MarkAsCore>() {
367                insignificance = default_importance() / 2;
368            }
369            if registration.contains::<MarkAsSignificant>() {
370                insignificance = default_importance() / 4;
371            }
372            if registration.contains::<ReflectResource>() {
373                is_resource = true
374            }
375            if registration.contains::<ReflectComponent>() {
376                is_component = true
377            }
378        }
379
380        let type_id = self.lad_id_from_type_id(type_info.type_id());
381        let lad_type = LadTypeDefinition {
382            identifier: type_info
383                .type_path_table()
384                .ident()
385                .unwrap_or_default()
386                .to_string(),
387            generics: type_info
388                .generics()
389                .iter()
390                .map(|param| LadGeneric {
391                    type_id: self.lad_id_from_type_id(param.type_id()),
392                    name: param.name().to_string(),
393                })
394                .collect(),
395            documentation: type_info.docs().map(|s| s.to_string()),
396            associated_functions: Vec::new(),
397            crate_: type_info
398                .type_path_table()
399                .crate_name()
400                .map(|s| s.to_owned()),
401            path: type_info.type_path_table().path().to_owned(),
402            layout: self.lad_layout_from_type_info(type_info),
403            generated,
404            insignificance,
405            metadata: LadTypeMetadata {
406                is_component,
407                is_resource,
408                is_reflect,
409                mapped_to_primitive_kind: primitive_from_type_id(type_info.type_id()),
410                misc: Default::default(),
411            },
412        };
413        self.file.types.insert(type_id, lad_type);
414        self
415    }
416
417    /// Adds all nested types within the given `ThroughTypeInfo`.
418    pub fn add_through_type_info(&mut self, type_info: &ThroughTypeInfo) -> &mut Self {
419        match type_info {
420            ThroughTypeInfo::UntypedWrapper { through_type, .. } => {
421                self.add_type_info(through_type);
422            }
423            ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind {
424                TypedWrapperKind::Union(ti) => {
425                    for ti in ti {
426                        self.add_through_type_info(ti);
427                    }
428                }
429                TypedWrapperKind::Vec(ti) => {
430                    self.add_through_type_info(ti);
431                }
432                TypedWrapperKind::HashMap(til, tir) => {
433                    self.add_through_type_info(til);
434                    self.add_through_type_info(tir);
435                }
436                TypedWrapperKind::HashSet(t) => {
437                    self.add_through_type_info(t);
438                }
439                TypedWrapperKind::Array(ti, _) => {
440                    self.add_through_type_info(ti);
441                }
442                TypedWrapperKind::Option(ti) => {
443                    self.add_through_type_info(ti);
444                }
445                TypedWrapperKind::InteropResult(ti) => {
446                    self.add_through_type_info(ti);
447                }
448                TypedWrapperKind::Tuple(ti) => {
449                    for ti in ti {
450                        self.add_through_type_info(ti);
451                    }
452                }
453            },
454            ThroughTypeInfo::TypeInfo(type_info) => {
455                self.add_type_info(type_info);
456            }
457            ThroughTypeInfo::Primitive(_) => {}
458        }
459
460        self
461    }
462
463    /// Add a function definition to the LAD file.
464    /// Will overwrite any existing function definitions with the same function id.
465    ///
466    /// Parses argument and return specific docstrings as per: <https://github.com/rust-lang/rust/issues/57525>
467    ///
468    /// i.e. looks for blocks like:
469    /// ```rust,ignore
470    /// /// Arguments:
471    /// ///  * `arg_name`: docstring1
472    /// ///  * `arg_name2`: docstring2
473    /// ///
474    /// /// Returns:
475    /// ///  * `return_name`: return docstring
476    /// ```
477    ///
478    /// And then removes them from the original block, instead putting it in each argument / return docstring
479    pub fn add_function_info(&mut self, function_info: &FunctionInfo) -> &mut Self {
480        let default_docstring = Cow::Owned("".into());
481        let (main_docstring, arg_docstrings, return_docstring) =
482            Self::split_docstring(function_info.docs.as_ref().unwrap_or(&default_docstring));
483
484        let mut identifier = function_info.name.as_ref();
485        let mut overload_index = None;
486        if identifier.contains("-") {
487            let mut parts = identifier.split("-");
488            if let Some(less_overload) = parts.next() {
489                identifier = less_overload;
490            }
491            if let Some(number) = parts.next() {
492                overload_index = number.parse::<usize>().ok()
493            }
494        }
495
496        let function_id = self.lad_function_id_from_info(function_info);
497        let lad_function = LadFunction {
498            identifier: identifier.to_owned().into(),
499            overload_index,
500            arguments: function_info
501                .arg_info
502                .clone()
503                .into_iter()
504                .map(|arg| {
505                    let kind = match &arg.type_info {
506                        Some(through_type) => self.lad_type_kind_from_through_type(through_type),
507                        None => {
508                            LadFieldOrVariableKind::Unknown(self.lad_id_from_type_id(arg.type_id))
509                        }
510                    };
511                    LadArgument {
512                        kind,
513                        documentation: arg_docstrings.iter().find_map(|(name, doc)| {
514                            (Some(name.as_str()) == arg.name.as_deref())
515                                .then_some(Cow::Owned(doc.clone()))
516                        }),
517                        name: arg.name,
518                    }
519                })
520                .collect(),
521            return_type: LadArgument {
522                name: return_docstring.as_ref().cloned().map(|(n, _)| n.into()),
523                documentation: return_docstring.map(|(_, v)| v.into()),
524                kind: function_info
525                    .return_info
526                    .type_info
527                    .clone()
528                    .map(|info| self.lad_type_kind_from_through_type(&info))
529                    .unwrap_or_else(|| {
530                        LadFieldOrVariableKind::Unknown(
531                            self.lad_id_from_type_id(function_info.return_info.type_id),
532                        )
533                    }),
534            },
535            documentation: (!main_docstring.is_empty()).then_some(main_docstring.into()),
536            namespace: match function_info.namespace {
537                Namespace::Global => LadFunctionNamespace::Global,
538                Namespace::OnType(type_id) => {
539                    LadFunctionNamespace::Type(self.lad_id_from_type_id(type_id))
540                }
541            },
542            metadata: LadFunctionMetadata {
543                is_operator: ScriptOperatorNames::parse(identifier).is_some(),
544                misc: Default::default(),
545            },
546        };
547        self.file.functions.insert(function_id, lad_function);
548        self
549    }
550
551    /// Set the markdown description of the LAD file.
552    pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
553        self.file.description = Some(description.into());
554        self
555    }
556
557    fn has_unknowns(&self, type_id: TypeId) -> bool {
558        if primitive_from_type_id(type_id).is_some() {
559            return false;
560        }
561
562        let type_info = match self.type_registry.get_type_info(type_id) {
563            Some(info) => info,
564            None => return true,
565        };
566        let inner_type_ids: Vec<_> = match type_info {
567            TypeInfo::Struct(struct_info) => {
568                struct_info.generics().iter().map(|g| g.type_id()).collect()
569            }
570            TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info
571                .generics()
572                .iter()
573                .map(|g| g.type_id())
574                .collect(),
575            TypeInfo::Tuple(tuple_info) => {
576                tuple_info.generics().iter().map(|g| g.type_id()).collect()
577            }
578            TypeInfo::List(list_info) => vec![list_info.item_ty().id()],
579            TypeInfo::Array(array_info) => vec![array_info.item_ty().id()],
580            TypeInfo::Map(map_info) => vec![map_info.key_ty().id(), map_info.value_ty().id()],
581            TypeInfo::Set(set_info) => vec![set_info.value_ty().id()],
582            TypeInfo::Enum(enum_info) => enum_info.generics().iter().map(|g| g.type_id()).collect(),
583            TypeInfo::Opaque(_) => vec![],
584        };
585
586        inner_type_ids.iter().any(|id| self.has_unknowns(*id))
587    }
588
589    /// Build the finalized and optimized LAD file.
590    pub fn build(&mut self) -> LadFile {
591        let mut file = std::mem::replace(&mut self.file, LadFile::new());
592
593        if self.exclude_types_involving_unregistered_types {
594            let mut to_remove = HashSet::new();
595            for reg in self.type_registry.iter() {
596                let type_id = reg.type_id();
597                if self.has_unknowns(type_id) {
598                    to_remove.insert(self.lad_id_from_type_id(type_id));
599                }
600            }
601
602            // remove those type ids
603            file.types.retain(|id, _| !to_remove.contains(id));
604        }
605
606        // associate functions on type namespaces with their types
607        for (function_id, function) in file.functions.iter() {
608            match &function.namespace {
609                LadFunctionNamespace::Type(type_id) => {
610                    if let Some(t) = file.types.get_mut(type_id) {
611                        t.associated_functions.push(function_id.clone());
612                    } else {
613                        warn!(
614                            "Function {} is on type {}, but the type is not registered in the LAD file.",
615                            function_id, type_id
616                        );
617                    }
618                }
619                LadFunctionNamespace::Global => {}
620            }
621        }
622
623        if self.sorted {
624            file.types.sort_by(|ak, av, bk, bv| {
625                let complexity_a: usize = av
626                    .path
627                    .char_indices()
628                    .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1))
629                    .sum();
630                let complexity_b = bv
631                    .path
632                    .char_indices()
633                    .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1))
634                    .sum();
635
636                let has_functions_a = !av.associated_functions.is_empty();
637                let has_functions_b = !bv.associated_functions.is_empty();
638
639                let ordered_by_name = ak.cmp(bk);
640                let ordered_by_generics_complexity = complexity_a.cmp(&complexity_b);
641                let ordered_by_generated = av.generated.cmp(&bv.generated);
642                let ordered_by_having_functions = has_functions_b.cmp(&has_functions_a);
643                let ordered_by_significance = av.insignificance.cmp(&bv.insignificance);
644
645                ordered_by_significance
646                    .then(ordered_by_having_functions)
647                    .then(ordered_by_generics_complexity)
648                    .then(ordered_by_name)
649                    .then(ordered_by_generated)
650            });
651
652            file.functions.sort_keys();
653        }
654
655        file
656    }
657
658    /// Checks if a line is one of:
659    /// - `# key:`
660    /// - `key:`
661    /// - `key`
662    /// - `## key`
663    ///
664    /// Or similar patterns
665    fn is_docstring_delimeter(key: &str, line: &str) -> bool {
666        line.trim()
667            .trim_start_matches("#")
668            .trim_end_matches(":")
669            .trim()
670            .eq_ignore_ascii_case(key)
671    }
672
673    /// Parses lines of the pattern:
674    /// * `arg` : val
675    ///
676    /// returning (arg,val) without markup
677    fn parse_arg_docstring(line: &str) -> Option<(&str, &str)> {
678        let regex =
679            regex::Regex::new(r#"\s*\*\s*`(?<arg>[^`]+)`\s*[:-]\s*(?<val>.+[^\s]).*$"#).ok()?;
680        let captures = regex.captures(line)?;
681        let arg = captures.name("arg")?;
682        let val = captures.name("val")?;
683
684        Some((arg.as_str(), val.as_str()))
685    }
686
687    /// Splits the docstring, into the following:
688    /// - The main docstring
689    /// - The argument docstrings
690    /// - The return docstring
691    ///
692    /// While removing any prefixes
693    fn split_docstring(
694        docstring: &str,
695    ) -> (String, Vec<(String, String)>, Option<(String, String)>) {
696        // find a line containing only `Arguments:` ignoring spaces and markdown headings
697        let lines = docstring.lines().collect::<Vec<_>>();
698
699        // this must exist for us to parse any of the areas
700        let argument_line_idx = match lines
701            .iter()
702            .enumerate()
703            .find_map(|(idx, l)| Self::is_docstring_delimeter("arguments", l).then_some(idx))
704        {
705            Some(a) => a,
706            None => return (docstring.to_owned(), vec![], None),
707        };
708
709        // this can, not exist, if arguments area does
710        let return_line_idx = lines.iter().enumerate().find_map(|(idx, l)| {
711            (Self::is_docstring_delimeter("returns", l)
712                || Self::is_docstring_delimeter("return", l))
713            .then_some(idx)
714        });
715
716        let return_area_idx = return_line_idx.unwrap_or(usize::MAX);
717        let return_area_first = argument_line_idx > return_area_idx;
718        let argument_range = match return_area_first {
719            true => argument_line_idx..lines.len(),
720            false => argument_line_idx..return_area_idx,
721        };
722        let return_range = match return_area_first {
723            true => return_area_idx..argument_line_idx,
724            false => return_area_idx..lines.len(),
725        };
726        let non_main_area =
727            min(return_area_idx, argument_line_idx)..max(return_area_idx, argument_line_idx);
728
729        let parsed_lines = lines
730            .iter()
731            .enumerate()
732            .map(|(i, l)| {
733                match Self::parse_arg_docstring(l) {
734                    Some(parsed) => {
735                        // figure out if it's in the argument, return or neither of the areas
736                        // if return area doesn't exist assign everything to arguments
737                        let in_argument_range = argument_range.contains(&i);
738                        let in_return_range = return_range.contains(&i);
739                        (l, Some((in_argument_range, in_return_range, parsed)))
740                    }
741                    None => (l, None),
742                }
743            })
744            .collect::<Vec<_>>();
745
746        // collect all argument docstrings, and the first return docstring, removing those lines from the docstring (and the argument/return headers)
747        // any other ones leave alone
748        let main_docstring = parsed_lines
749            .iter()
750            .enumerate()
751            .filter_map(|(i, (l, parsed))| {
752                ((!non_main_area.contains(&i) || !l.trim().is_empty())
753                    && (i != return_area_idx && i != argument_line_idx)
754                    && (parsed.is_none() || parsed.is_some_and(|(a, b, _)| !a && !b)))
755                .then_some((**l).to_owned())
756            })
757            .collect::<Vec<_>>();
758
759        let arg_docstrings = parsed_lines
760            .iter()
761            .filter_map(|(_l, parsed)| {
762                parsed.and_then(|(is_arg, is_return, (a, b))| {
763                    (is_arg && !is_return).then_some((a.to_owned(), b.to_owned()))
764                })
765            })
766            .collect();
767
768        let return_docstring = parsed_lines.iter().find_map(|(_l, parsed)| {
769            parsed.and_then(|(is_arg, is_return, (a, b))| {
770                (!is_arg && is_return).then_some((a.to_owned(), b.to_owned()))
771            })
772        });
773
774        (main_docstring.join("\n"), arg_docstrings, return_docstring)
775    }
776
777    fn variant_identifier_for_non_enum(type_info: &TypeInfo) -> Cow<'static, str> {
778        type_info
779            .type_path_table()
780            .ident()
781            .unwrap_or_else(|| type_info.type_path_table().path())
782            .into()
783    }
784
785    fn struct_variant_from_named_fields<'a, I: Iterator<Item = &'a NamedField>>(
786        &mut self,
787        name: Cow<'static, str>,
788        fields: I,
789    ) -> LadVariant {
790        LadVariant::Struct {
791            name,
792            fields: fields
793                .map(|field| LadNamedField {
794                    name: field.name().to_string(),
795                    type_: self.lad_type_kind_from_type_id(field.type_id()),
796                })
797                .collect(),
798        }
799    }
800
801    fn tuple_struct_variant_from_fields<'a, I: Iterator<Item = &'a UnnamedField>>(
802        &mut self,
803        name: Cow<'static, str>,
804        fields: I,
805    ) -> LadVariant {
806        LadVariant::TupleStruct {
807            name,
808            fields: fields
809                .map(|field| LadField {
810                    type_: self.lad_type_kind_from_type_id(field.type_id()),
811                })
812                .collect(),
813        }
814    }
815
816    fn lad_layout_from_type_info(&mut self, type_info: &TypeInfo) -> LadTypeLayout {
817        match type_info {
818            TypeInfo::Struct(struct_info) => {
819                let fields = (0..struct_info.field_len()).filter_map(|i| struct_info.field_at(i));
820
821                LadTypeLayout::MonoVariant(self.struct_variant_from_named_fields(
822                    Self::variant_identifier_for_non_enum(type_info),
823                    fields,
824                ))
825            }
826            TypeInfo::TupleStruct(tuple_struct_info) => {
827                let fields = (0..tuple_struct_info.field_len())
828                    .filter_map(|i| tuple_struct_info.field_at(i));
829
830                LadTypeLayout::MonoVariant(self.tuple_struct_variant_from_fields(
831                    Self::variant_identifier_for_non_enum(type_info),
832                    fields,
833                ))
834            }
835            TypeInfo::Enum(enum_info) => {
836                let mut variants = Vec::new();
837                for i in 0..enum_info.variant_len() {
838                    if let Some(variant) = enum_info.variant_at(i) {
839                        let variant_name = variant.name();
840                        let variant = match variant {
841                            bevy_reflect::VariantInfo::Struct(struct_variant_info) => {
842                                let fields = (0..struct_variant_info.field_len())
843                                    .filter_map(|i| struct_variant_info.field_at(i));
844
845                                self.struct_variant_from_named_fields(variant_name.into(), fields)
846                            }
847                            bevy_reflect::VariantInfo::Tuple(tuple_variant_info) => {
848                                let fields = (0..tuple_variant_info.field_len())
849                                    .filter_map(|i| tuple_variant_info.field_at(i));
850
851                                self.tuple_struct_variant_from_fields(variant_name.into(), fields)
852                            }
853                            bevy_reflect::VariantInfo::Unit(_) => LadVariant::Unit {
854                                name: variant_name.into(),
855                            },
856                        };
857                        variants.push(variant);
858                    }
859                }
860                LadTypeLayout::Enum(variants)
861            }
862            _ => LadTypeLayout::Opaque,
863        }
864    }
865
866    /// Should only be used on fields, as those are never going to contain
867    /// untyped structures, i.e. are going to be fully reflectable
868    fn lad_type_kind_from_type_id(&mut self, type_id: TypeId) -> LadFieldOrVariableKind {
869        if let Some(type_info) = self.type_registry.get_type_info(type_id) {
870            let through_type_info = into_through_type_info(type_info);
871            self.lad_type_kind_from_through_type(&through_type_info)
872        } else {
873            LadFieldOrVariableKind::Unknown(self.lad_id_from_type_id(type_id))
874        }
875    }
876
877    /// Figures out whether the type is a primitive or not and creates the right type id
878    fn lad_id_from_type_id(&mut self, type_id: TypeId) -> LadTypeId {
879        // a special exception
880        if type_id == std::any::TypeId::of::<World>() {
881            return LadTypeId::new_string_id("World".into());
882        }
883
884        if let Some(lad_id) = self.type_id_mapping.get(&type_id) {
885            return lad_id.clone();
886        }
887
888        let new_id = match primitive_from_type_id(type_id) {
889            Some(primitive) => LadTypeId::new_string_id(primitive.to_string().into()),
890            None => {
891                if let Some(info) = self.type_registry.get_type_info(type_id) {
892                    LadTypeId::new_string_id(info.type_path_table().path().into())
893                } else {
894                    LadTypeId::new_string_id(format!("{type_id:?}").into())
895                }
896            }
897        };
898
899        self.type_id_mapping.insert(type_id, new_id.clone());
900        new_id
901    }
902
903    fn lad_function_id_from_info(&mut self, function_info: &FunctionInfo) -> LadFunctionId {
904        let namespace_string = match function_info.namespace {
905            bevy_mod_scripting_bindings::function::namespace::Namespace::Global => "".to_string(),
906            bevy_mod_scripting_bindings::function::namespace::Namespace::OnType(type_id) => {
907                self.lad_id_from_type_id(type_id).to_string()
908            }
909        };
910
911        LadFunctionId::new_string_id(format!("{}::{}", namespace_string, function_info.name))
912    }
913
914    fn lad_type_kind_from_through_type(
915        &mut self,
916        through_type: &ThroughTypeInfo,
917    ) -> LadFieldOrVariableKind {
918        match through_type {
919            ThroughTypeInfo::UntypedWrapper {
920                through_type,
921                wrapper_kind,
922                ..
923            } => match wrapper_kind {
924                UntypedWrapperKind::Ref => {
925                    LadFieldOrVariableKind::Ref(self.lad_id_from_type_id(through_type.type_id()))
926                }
927                UntypedWrapperKind::Mut => {
928                    LadFieldOrVariableKind::Mut(self.lad_id_from_type_id(through_type.type_id()))
929                }
930                UntypedWrapperKind::Val => {
931                    LadFieldOrVariableKind::Val(self.lad_id_from_type_id(through_type.type_id()))
932                }
933            },
934            ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind {
935                TypedWrapperKind::Vec(through_type_info) => LadFieldOrVariableKind::Vec(Box::new(
936                    self.lad_type_kind_from_through_type(through_type_info),
937                )),
938                TypedWrapperKind::HashMap(through_type_info, through_type_info1) => {
939                    LadFieldOrVariableKind::HashMap(
940                        Box::new(self.lad_type_kind_from_through_type(through_type_info)),
941                        Box::new(self.lad_type_kind_from_through_type(through_type_info1)),
942                    )
943                }
944                TypedWrapperKind::HashSet(through_type_info) => LadFieldOrVariableKind::HashSet(
945                    Box::new(self.lad_type_kind_from_through_type(through_type_info)),
946                ),
947                TypedWrapperKind::Array(through_type_info, size) => LadFieldOrVariableKind::Array(
948                    Box::new(self.lad_type_kind_from_through_type(through_type_info)),
949                    *size,
950                ),
951                TypedWrapperKind::Option(through_type_info) => LadFieldOrVariableKind::Option(
952                    Box::new(self.lad_type_kind_from_through_type(through_type_info)),
953                ),
954                TypedWrapperKind::InteropResult(through_type_info) => {
955                    LadFieldOrVariableKind::InteropResult(Box::new(
956                        self.lad_type_kind_from_through_type(through_type_info),
957                    ))
958                }
959                TypedWrapperKind::Tuple(through_type_infos) => LadFieldOrVariableKind::Tuple(
960                    through_type_infos
961                        .iter()
962                        .map(|through_type_info| {
963                            self.lad_type_kind_from_through_type(through_type_info)
964                        })
965                        .collect(),
966                ),
967                TypedWrapperKind::Union(through_type_infos) => LadFieldOrVariableKind::Union(
968                    through_type_infos
969                        .iter()
970                        .map(|through_type_info| {
971                            self.lad_type_kind_from_through_type(through_type_info)
972                        })
973                        .collect(),
974                ),
975            },
976            ThroughTypeInfo::TypeInfo(type_info) => {
977                match primitive_from_type_id(type_info.type_id()) {
978                    Some(primitive) => LadFieldOrVariableKind::Primitive(primitive),
979                    None => LadFieldOrVariableKind::Unknown(
980                        self.lad_id_from_type_id(type_info.type_id()),
981                    ),
982                }
983            }
984            ThroughTypeInfo::Primitive(reflection_primitive_kind) => {
985                LadFieldOrVariableKind::Primitive(reflection_primitive_kind.clone())
986            }
987        }
988    }
989}
990
991#[cfg(test)]
992mod test {
993    use std::collections::HashMap;
994
995    use bevy_mod_scripting_bindings::{
996        Union, Val,
997        docgen::info::GetFunctionInfo,
998        function::{
999            from::Ref,
1000            namespace::{GlobalNamespace, IntoNamespace},
1001        },
1002    };
1003    use bevy_reflect::Reflect;
1004
1005    use super::*;
1006
1007    /// normalize line endings etc..
1008    fn normalize_file(file: &mut String) {
1009        *file = file.replace("\r\n", "\n");
1010    }
1011
1012    #[test]
1013    fn test_empty_lad_file_serializes_correctly() {
1014        let lad_file = LadFile::new();
1015        let serialized = serialize_lad_file(&lad_file, false).unwrap();
1016        let deserialized = parse_lad_file(&serialized).unwrap();
1017        assert_eq!(lad_file, deserialized);
1018        assert_eq!(deserialized.version, ladfile::LAD_VERSION);
1019    }
1020
1021    #[test]
1022    fn parse_docstrings_is_resistant_to_whitespace() {
1023        pretty_assertions::assert_eq!(
1024            LadFileBuilder::parse_arg_docstring("* `arg` : doc"),
1025            Some(("arg", "doc"))
1026        );
1027        pretty_assertions::assert_eq!(
1028            LadFileBuilder::parse_arg_docstring("  * `arg` - doc"),
1029            Some(("arg", "doc"))
1030        );
1031        pretty_assertions::assert_eq!(
1032            LadFileBuilder::parse_arg_docstring("   *   `arg`   :    doc     "),
1033            Some(("arg", "doc"))
1034        );
1035    }
1036
1037    #[test]
1038    fn docstring_delimeter_detection_is_flexible() {
1039        assert!(LadFileBuilder::is_docstring_delimeter(
1040            "arguments",
1041            "arguments"
1042        ));
1043        assert!(LadFileBuilder::is_docstring_delimeter(
1044            "arguments",
1045            "Arguments:"
1046        ));
1047        assert!(LadFileBuilder::is_docstring_delimeter(
1048            "arguments",
1049            "## Arguments"
1050        ));
1051        assert!(LadFileBuilder::is_docstring_delimeter(
1052            "arguments",
1053            "## Arguments:"
1054        ));
1055        assert!(LadFileBuilder::is_docstring_delimeter(
1056            "arguments",
1057            "Arguments"
1058        ));
1059    }
1060
1061    /// Helper function to assert that splitting the docstring produces the expected output.
1062    fn assert_docstring_split(
1063        input: &str,
1064        expected_main: &str,
1065        expected_args: &[(&str, &str)],
1066        expected_return: Option<(&str, &str)>,
1067        test_name: &str,
1068    ) {
1069        let (main, args, ret) = LadFileBuilder::split_docstring(input);
1070
1071        pretty_assertions::assert_eq!(
1072            main,
1073            expected_main,
1074            "main docstring was incorrect - {}",
1075            test_name
1076        );
1077
1078        let expected_args: Vec<(String, String)> = expected_args
1079            .iter()
1080            .map(|(a, b)| (a.to_string(), b.to_string()))
1081            .collect();
1082        pretty_assertions::assert_eq!(
1083            args,
1084            expected_args,
1085            "argument docstring was incorrect - {}",
1086            test_name
1087        );
1088
1089        let expected_ret = expected_return.map(|(a, b)| (a.to_string(), b.to_string()));
1090        pretty_assertions::assert_eq!(
1091            ret,
1092            expected_ret,
1093            "return docstring was incorrect - {}",
1094            test_name
1095        );
1096    }
1097
1098    #[test]
1099    fn docstrings_parse_correctly_from_various_formats() {
1100        assert_docstring_split(
1101            r#"
1102                ## Hello
1103                Arguments: 
1104                    * `arg1` - some docs
1105                    * `arg2` : some more docs
1106                # Returns
1107                    * `return` : return docs
1108            "#
1109            .trim(),
1110            "## Hello",
1111            &[("arg1", "some docs"), ("arg2", "some more docs")],
1112            Some(("return", "return docs")),
1113            "normal docstring",
1114        );
1115        assert_docstring_split(
1116            r#"
1117                Arguments: 
1118                    * `arg1` - some docs
1119                    * `arg2` : some more docs
1120                Returns
1121                    * `return` : return docs
1122            "#
1123            .trim(),
1124            "",
1125            &[("arg1", "some docs"), ("arg2", "some more docs")],
1126            Some(("return", "return docs")),
1127            "empty main docstring",
1128        );
1129        assert_docstring_split(
1130            r#"
1131                Arguments: 
1132                    * `arg1` - some docs
1133                    * `arg2` : some more docs
1134            "#
1135            .trim(),
1136            "",
1137            &[("arg1", "some docs"), ("arg2", "some more docs")],
1138            None,
1139            "no return docstring",
1140        );
1141        assert_docstring_split(
1142            r#"
1143                Returns
1144                    * `return` : return docs
1145            "#
1146            .trim(),
1147            r#"
1148                Returns
1149                    * `return` : return docs
1150            "#
1151            .trim(),
1152            &[],
1153            None,
1154            "no argument docstring",
1155        );
1156        assert_docstring_split(
1157            r#"
1158                ## Hello
1159            "#
1160            .trim(),
1161            "## Hello",
1162            &[],
1163            None,
1164            "no argument or return docstring",
1165        );
1166        // return first
1167        assert_docstring_split(
1168            r#"
1169                Returns
1170                    * `return` : return docs
1171                Arguments: 
1172                    * `arg1` - some docs
1173                    * `arg2` : some more docs
1174            "#
1175            .trim(),
1176            "",
1177            &[("arg1", "some docs"), ("arg2", "some more docs")],
1178            Some(("return", "return docs")),
1179            "return first",
1180        );
1181        // whitespace in between
1182        assert_docstring_split(
1183            r#"
1184                ## Hello
1185
1186
1187                Arguments: 
1188                    * `arg1` - some docs
1189                    * `arg2` : some more docs
1190
1191                Returns
1192                    * `return` : return docs
1193            "#
1194            .trim(),
1195            "## Hello\n\n",
1196            &[("arg1", "some docs"), ("arg2", "some more docs")],
1197            Some(("return", "return docs")),
1198            "whitespace in between",
1199        );
1200    }
1201
1202    #[test]
1203    fn test_serializes_as_expected() {
1204        let mut type_registry = TypeRegistry::default();
1205
1206        #[derive(Reflect)]
1207        /// I am a struct
1208        struct GenericStructType<T> {
1209            /// hello from field
1210            field: usize,
1211            /// hello from field 2
1212            field2: T,
1213        }
1214
1215        impl TypedThrough for GenericStructType<usize> {
1216            fn through_type_info() -> ThroughTypeInfo {
1217                ThroughTypeInfo::TypeInfo(Self::type_info())
1218            }
1219        }
1220
1221        #[derive(Reflect)]
1222        /// I am a simple plain struct type
1223        struct PlainStructType {
1224            int_field: usize,
1225        }
1226
1227        impl TypedThrough for PlainStructType {
1228            fn through_type_info() -> ThroughTypeInfo {
1229                ThroughTypeInfo::TypeInfo(Self::type_info())
1230            }
1231        }
1232
1233        #[derive(Reflect)]
1234        /// I am a unit test type
1235        struct UnitType;
1236
1237        impl TypedThrough for UnitType {
1238            fn through_type_info() -> ThroughTypeInfo {
1239                ThroughTypeInfo::TypeInfo(Self::type_info())
1240            }
1241        }
1242
1243        #[derive(Reflect)]
1244        /// I am a tuple test type
1245        struct TupleStructType(pub usize, #[doc = "hello"] pub String);
1246
1247        #[derive(Reflect)]
1248        enum EnumType {
1249            /// hello from variant
1250            Unit,
1251            /// hello from variant 2
1252            Struct {
1253                /// hello from field
1254                field: usize,
1255            },
1256            /// hello from variant 3
1257            TupleStruct(usize, #[doc = "asd"] String),
1258        }
1259
1260        type_registry.register::<GenericStructType<usize>>();
1261        type_registry.register::<UnitType>();
1262        type_registry.register::<TupleStructType>();
1263        type_registry.register::<EnumType>();
1264        type_registry.register::<PlainStructType>();
1265
1266        let plain_struct_function =
1267            |_: Ref<PlainStructType>, _: usize| PlainStructType { int_field: 2 };
1268        let plain_struct_function_info = plain_struct_function.get_function_info(
1269            "plain_struct_function".into(),
1270            PlainStructType::into_namespace(),
1271        );
1272
1273        let function = |_: ReflectReference, _: usize| 2usize;
1274        let function_info = function
1275            .get_function_info(
1276                "hello_world".into(),
1277                GenericStructType::<usize>::into_namespace(),
1278            )
1279            .with_docs("hello docs");
1280
1281        let function_with_complex_args =
1282            |_: ReflectReference, _: (usize, String), _: Option<Vec<Ref<EnumType>>>| 2usize;
1283        let function_with_complex_args_info = function_with_complex_args
1284            .get_function_info("hello_world".into(), GenericStructType::<usize>::into_namespace())
1285            .with_arg_names(&["ref_", "tuple", "option_vec_ref_wrapper"])
1286            .with_docs(
1287                "Arguments: ".to_owned()
1288                    + "\n"
1289                    + " * `ref_`: I am some docs for argument 1"
1290                    + "\n"
1291                    + " * `tuple`: I am some docs for argument 2"
1292                    + "\n"
1293                    + " * `option_vec_ref_wrapper`: I am some docs for argument 3"
1294                    + "\n"
1295                    + "Returns: "
1296                    + "\n"
1297                    + " * `return`: I am some docs for the return type, I provide a name for the return value too",
1298            );
1299
1300        let global_function = |_: usize| 2usize;
1301        let global_function_info = global_function
1302            .get_function_info("hello_world".into(), GlobalNamespace::into_namespace())
1303            .with_arg_names(&["arg1"]);
1304
1305        let mut lad_file = LadFileBuilder::new(&type_registry)
1306            .set_description("## Hello gentlemen\n I am  markdown file.\n - hello\n - world")
1307            .set_sorted(true)
1308            .add_function_info(&plain_struct_function_info)
1309            .add_function_info(&function_info)
1310            .add_function_info(&global_function_info)
1311            .add_function_info(&function_with_complex_args_info)
1312            .add_type::<GenericStructType<usize>>()
1313            .add_type::<UnitType>()
1314            .add_type::<TupleStructType>()
1315            .add_type::<PlainStructType>()
1316            .add_type_info(EnumType::type_info())
1317            .add_instance::<Val<GenericStructType<usize>>>("my_static_instance", true)
1318            .add_instance::<Vec<Val<UnitType>>>("my_non_static_instance", false)
1319            .add_instance::<HashMap<String, Union<String, String>>>("map", false)
1320            .build();
1321
1322        // normalize the version so we don't have to update it every time
1323        lad_file.version = "{{version}}".into();
1324        let mut serialized = serialize_lad_file(&lad_file, true).unwrap();
1325
1326        normalize_file(&mut serialized);
1327
1328        if std::env::var("BLESS_MODE").is_ok() {
1329            let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
1330            let path_to_test_assets = std::path::Path::new(&manifest_dir)
1331                .join("..")
1332                .join("ladfile")
1333                .join("test_assets");
1334
1335            println!("Blessing test file at {path_to_test_assets:?}");
1336            std::fs::write(path_to_test_assets.join("test.lad.json"), &serialized).unwrap();
1337            panic!("Blessed test file, please rerun the test");
1338        }
1339
1340        let mut expected = ladfile::EXAMPLE_LADFILE.to_string();
1341        normalize_file(&mut expected);
1342
1343        pretty_assertions::assert_eq!(serialized.trim(), expected.trim(),);
1344    }
1345
1346    #[test]
1347    fn test_asset_deserializes_correctly() {
1348        let asset = ladfile::EXAMPLE_LADFILE.to_string();
1349        let deserialized = parse_lad_file(&asset).unwrap();
1350        assert_eq!(deserialized.version, "{{version}}");
1351    }
1352
1353    #[test]
1354    fn test_nested_unregistered_generic_is_removed() {
1355        let mut type_registry = TypeRegistry::default();
1356
1357        #[derive(Reflect)]
1358        #[reflect(no_field_bounds, from_reflect = false)]
1359        struct StructType<T> {
1360            #[reflect(ignore)]
1361            phantom: std::marker::PhantomData<T>,
1362        }
1363
1364        #[derive(Reflect)]
1365        struct Blah;
1366
1367        type_registry.register::<StructType<Blah>>();
1368
1369        let lad_file = LadFileBuilder::new_empty(&type_registry)
1370            .set_sorted(true)
1371            .set_exclude_including_unregistered(true)
1372            .add_type::<StructType<Blah>>()
1373            .build();
1374
1375        assert_eq!(lad_file.types.len(), 0);
1376    }
1377}