emmylua_doc_cli 0.22.0

A command-line tool for generating lua documentation.
Documentation
use std::path::Path;

use emmylua_code_analysis::{
    DbIndex, FileId, LuaMemberKey, LuaMemberOwner, LuaSemanticDeclId, LuaType, LuaTypeCache,
    ModuleInfo, RenderLevel, humanize_type,
};
use emmylua_parser::VisibilityKind;
use tera::Tera;

use crate::markdown_generator::{
    escape_type_name,
    markdown_types::{Doc, IndexStruct, MemberDoc, MkdocsIndex},
    render::{render_const_type, render_function_type},
};

use super::collect_property;

pub fn generate_module_markdown(
    db: &DbIndex,
    tl: &Tera,
    module: &ModuleInfo,
    output: &Path,
    mkdocs_index: &mut MkdocsIndex,
) -> Option<()> {
    check_filter(db, module.file_id)?;

    let mut context = tera::Context::new();
    let mut doc = Doc {
        name: module.full_module_name.clone(),
        ..Default::default()
    };
    let property_owner_id = module.semantic_id.clone();
    if let Some(property_id) = property_owner_id {
        doc.property = collect_property(db, property_id);
    }

    let export_typ = module.export_type.clone()?;
    match &export_typ {
        LuaType::Def(type_id) => {
            let member_owner = LuaMemberOwner::Type(type_id.clone());
            let type_simple_name = type_id.get_simple_name();
            generate_member_owner_module(db, member_owner, type_simple_name, &mut doc);
        }
        LuaType::TableConst(t) => {
            let member_owner = LuaMemberOwner::Element(t.clone());
            generate_member_owner_module(db, member_owner, "M", &mut doc);
        }
        LuaType::Instance(i) => {
            let member_owner = LuaMemberOwner::Element(i.get_range().clone());
            generate_member_owner_module(db, member_owner, "M", &mut doc);
        }
        _ => {}
    }

    context.insert("doc", &doc);

    let render_text = match tl.render("lua_module_template.tl", &context) {
        Ok(text) => text,
        Err(e) => {
            log::error!("Failed to render template: {}", e);
            return None;
        }
    };

    let file_name = format!("{}.md", escape_type_name(&module.full_module_name));
    mkdocs_index.modules.push(IndexStruct {
        name: module.full_module_name.clone(),
        file: format!("modules/{}", file_name.clone()),
    });

    let outpath = output.join(file_name);
    log::info!("Writing module file: {}", outpath.display());
    match std::fs::write(outpath, render_text) {
        Ok(_) => {}
        Err(e) => {
            log::error!("Failed to write file: {}", e);
            return None;
        }
    }
    Some(())
}

fn check_filter(db: &DbIndex, file_id: FileId) -> Option<()> {
    let module = db.get_module_index().get_module(file_id)?;
    if module.workspace_id.is_main() {
        return Some(());
    }

    None
}

pub fn generate_member_owner_module(
    db: &DbIndex,
    member_owner: LuaMemberOwner,
    owner_name: &str,
    doc: &mut Doc,
) -> Option<()> {
    let members = db.get_member_index().get_sorted_members(&member_owner);
    let mut method_members: Vec<MemberDoc> = Vec::new();
    let mut field_members: Vec<MemberDoc> = Vec::new();

    if let Some(members) = members {
        for member in members {
            let member_type = db
                .get_type_index()
                .get_type_cache(&member.get_id().into())
                .unwrap_or(&LuaTypeCache::InferType(LuaType::Unknown))
                .as_type();
            let member_id = member.get_id();
            let member_property_id = LuaSemanticDeclId::Member(member_id);
            let member_property = db.get_property_index().get_property(&member_property_id);
            if let Some(member_property) = member_property
                && member_property.visibility != VisibilityKind::Public
            {
                continue;
            }

            let member_property = collect_property(db, member_property_id);
            let member_key = member.get_key();
            let name = match member_key {
                LuaMemberKey::Name(name) => name,
                _ => continue,
            };

            let title_name = format!("{}.{}", owner_name, name);
            if member_type.is_function() {
                let func_name = format!("{}.{}", owner_name, name);
                let display = render_function_type(db, member_type, &func_name, false);
                method_members.push(MemberDoc {
                    name: title_name,
                    display,
                    property: member_property,
                });
            } else if member_type.is_const() {
                let display = render_const_type(db, member_type);
                field_members.push(MemberDoc {
                    name: title_name,
                    display: format!("```lua\n{}.{}: {}\n```\n", owner_name, name, display),
                    property: member_property,
                });
            } else {
                let typ_display = humanize_type(db, member_type, RenderLevel::Detailed);
                field_members.push(MemberDoc {
                    name: title_name,
                    display: format!("```lua\n{}.{} : {}\n```\n", owner_name, name, typ_display),
                    property: member_property,
                });
            }
        }
    }
    if !method_members.is_empty() {
        doc.methods = Some(method_members);
    }

    if !field_members.is_empty() {
        doc.fields = Some(field_members);
    }

    Some(())
}