tealr_doc_gen 0.4.0

A cli tool to create online documentation for apis made with tealr
use std::collections::HashSet;

use tealr::TypeWalker;

use crate::render_type::is_type_compatible;

pub fn warn_about_missing_exports(type_defs: &TypeWalker) {
    let mut missing = find_types_missing_export(type_defs);
    missing.sort_by_key(|(x, _)| x.name.to_string());
    let grouped = missing
        .into_iter()
        .fold(Vec::new(), |mut acc, (ty, used_by)| {
            match acc.last_mut() {
                None => {
                    acc.push((ty, vec![used_by]));
                }
                Some((current, users)) => {
                    if current.name == ty.name {
                        users.push(used_by);
                    } else {
                        acc.push((ty, vec![used_by]));
                    }
                }
            }
            acc
        });
    for (ty, used_by) in grouped {
        match used_by.as_slice() {
            [] => {
                eprintln!(
                    "Missing type from export: {}. But no matching use found? This sounds like a bug.",
                    ty.name
                );
            }
            [x] => {
                eprintln!("Missing type from export: {}. Used by: {}", ty.name, x);
            }
            [x, y @ ..] => {
                eprintln!(
                    "Missing type from export: {}. Used by: {} and {} others",
                    ty.name,
                    x,
                    y.len()
                );
            }
        }
    }
}

fn find_types_missing_export(walker: &TypeWalker) -> Vec<(tealr::SingleType, String)> {
    let mut missing_types = Vec::new();
    let all_exported_types = walker
        .iter()
        .map(|x| x.type_name().to_owned())
        .filter_map(|v| v.single().map(ToOwned::to_owned))
        .collect::<HashSet<_>>();
    let all_used_types = walker
        .iter()
        .flat_map(|x| {
            if let tealr::TypeGenerator::Record(record_generator) = x {
                let name = tealr::type_to_string(&record_generator.ty, false);
                let name2 = name.clone();
                record_generator
                    .fields
                    .iter()
                    .map(move |x| {
                        (
                            x.ty.clone(),
                            format!("{}.{}", name, String::from_utf8_lossy(&x.name)),
                        )
                    })
                    .chain(extract_all_function_types_from_record_generator(
                        record_generator,
                    ))
                    .chain(record_generator.static_fields.iter().map(|x| {
                        (
                            x.ty.clone(),
                            format!("{}.{}", name2, String::from_utf8_lossy(&x.name)),
                        )
                    }))
                    .collect::<HashSet<_>>()
            } else {
                HashSet::new()
            }
        })
        .flat_map(|(x, belongs_to)| {
            extract_all_singular_types_from_type(&x).map(move |v| (v, belongs_to.clone()))
        })
        .chain(walker.global_instances_off.iter().flat_map(|x| {
            extract_all_singular_types_from_type(&x.ty).map(move |x| {
                let name = format!("global.{}", x.name.clone());
                (x, name)
            })
        }))
        .filter(|(ty, _)| ty.kind == tealr::KindOfType::External)
        .collect::<HashSet<_>>();
    for (ty, belongs_to) in all_used_types {
        if !all_exported_types
            .iter()
            .any(|v| is_type_compatible(v, ty.to_owned()))
        {
            missing_types.push((ty, belongs_to));
        }
    }
    missing_types
}

fn extract_all_singular_types_from_type<'a>(
    ty: &tealr::Type,
) -> impl Iterator<Item = tealr::SingleType> + 'a {
    match ty {
        tealr::Type::Function(function_representation) => function_representation
            .params
            .iter()
            .flat_map(|x| extract_all_singular_types_from_type(&x.ty))
            .chain(
                function_representation
                    .returns
                    .iter()
                    .flat_map(extract_all_singular_types_from_type),
            )
            .collect::<HashSet<_>>(),
        tealr::Type::Single(single_type) => {
            std::iter::once(single_type.clone()).collect::<HashSet<_>>()
        }
        tealr::Type::Map(map_representation) => {
            extract_all_singular_types_from_type(&map_representation.key)
                .chain(extract_all_singular_types_from_type(
                    &map_representation.value,
                ))
                .collect::<HashSet<_>>()
        }
        tealr::Type::Tuple(vec) | tealr::Type::Or(vec) => vec
            .iter()
            .flat_map(extract_all_singular_types_from_type)
            .collect::<HashSet<_>>(),
        tealr::Type::Array(x) => extract_all_singular_types_from_type(x).collect::<HashSet<_>>(),
        tealr::Type::Variadic(x) => extract_all_singular_types_from_type(x).collect::<HashSet<_>>(),
    }
    .into_iter()
}

fn extract_all_function_types_from_record_generator(
    record_generator: &tealr::RecordGenerator,
) -> impl Iterator<Item = (tealr::Type, String)> + '_ {
    let name = tealr::type_to_string(&record_generator.ty, false).to_string();
    extract_types_from_exported_functions(&record_generator.functions, name.clone())
        .chain(extract_types_from_exported_functions(
            &record_generator.methods,
            name.clone(),
        ))
        .chain(extract_types_from_exported_functions(
            &record_generator.mut_functions,
            name.clone(),
        ))
        .chain(extract_types_from_exported_functions(
            &record_generator.mut_methods,
            name.clone(),
        ))
        .chain(extract_types_from_exported_functions(
            &record_generator.meta_function,
            name.clone(),
        ))
        .chain(extract_types_from_exported_functions(
            &record_generator.meta_method,
            name.clone(),
        ))
        .chain(extract_types_from_exported_functions(
            &record_generator.meta_function_mut,
            name.clone(),
        ))
        .chain(extract_types_from_exported_functions(
            &record_generator.meta_method_mut,
            name,
        ))
}

fn extract_types_from_exported_functions(
    exported_functions: &[tealr::ExportedFunction],
    record_name: String,
) -> impl Iterator<Item = (tealr::Type, String)> + '_ {
    exported_functions
        .iter()
        .flat_map(move |x| extract_types_from_exported_function(x, record_name.clone()))
}

fn extract_types_from_exported_function(
    exported_function: &tealr::ExportedFunction,
    record_name: String,
) -> impl Iterator<Item = (tealr::Type, String)> + '_ {
    let full_name = format!(
        "{}.{}",
        record_name,
        String::from_utf8_lossy(&exported_function.name)
    );
    let full_name2 = full_name.clone();
    exported_function
        .params
        .iter()
        .map(move |x| (x.ty.clone(), full_name.clone()))
        .chain(
            exported_function
                .returns
                .iter()
                .map(move |x| (x.to_owned(), full_name2.clone())),
        )
}