use std::collections::HashMap; use anyhow::Result; use regex::Regex; use serde_json::{Map, Value}; use full_moon::{parse as parse_luau_ast, visitors::Visitor}; mod doc; mod tag; mod visitor; use self::{doc::DocsFunctionParamLink, visitor::DocumentationVisitor}; pub fn parse_definitions(contents: &str) -> Result { // TODO: Properly handle the "declare class" syntax, for now we just skip it let mut no_declares = contents.to_string(); while let Some(dec) = no_declares.find("\ndeclare class") { let end = no_declares.find("\nend").unwrap(); let before = &no_declares[0..dec]; let after = &no_declares[end + 4..]; no_declares = format!("{before}{after}"); } let (regex, replacement) = ( Regex::new(r#"declare (?P\w+): "#).unwrap(), r#"export type $n = "#, ); let defs_ast = parse_luau_ast(®ex.replace_all(&no_declares, replacement))?; let mut visitor = DocumentationVisitor::new(); visitor.visit_ast(&defs_ast); Ok(visitor) } pub fn generate_docs_json_from_definitions(contents: &str, namespace: &str) -> Result { let visitor = parse_definitions(contents)?; /* Extract globals, functions, params, returns from the visitor Here we will also convert the plain names into proper namespaced names according to the spec at https://raw.githubusercontent.com/MaximumADHD/Roblox-Client-Tracker/roblox/api-docs/en-us.json */ let mut map = Map::new(); for (name, mut doc) in visitor.globals { doc.keys = doc .keys .iter() .map(|(key, value)| (key.clone(), format!("@{namespace}/{name}.{value}"))) .collect::>(); map.insert(format!("@{namespace}/{name}"), serde_json::to_value(doc)?); } for (name, mut doc) in visitor.functions { doc.params = doc .params .iter() .map(|param| DocsFunctionParamLink { name: param.name.clone(), documentation: format!( "@{namespace}/{}.{name}/param/{}", doc.global_name, param.documentation ), }) .collect::>(); doc.returns = doc .returns .iter() .map(|ret| format!("@{namespace}/{}.{name}/return/{ret}", doc.global_name)) .collect::>(); map.insert( format!("@{namespace}/{}.{name}", doc.global_name), serde_json::to_value(doc)?, ); } for (name, doc) in visitor.params { map.insert( format!( "@{namespace}/{}.{}/param/{name}", doc.global_name, doc.function_name ), serde_json::to_value(doc)?, ); } for (name, doc) in visitor.returns { map.insert( format!( "@{namespace}/{}.{}/return/{name}", doc.global_name, doc.function_name ), serde_json::to_value(doc)?, ); } Ok(Value::Object(map)) }