Skip to main content

intent_codegen/
typescript.rs

1//! TypeScript skeleton code generator.
2
3use intent_parser::ast;
4
5use crate::types::map_type;
6use crate::{Language, doc_text, format_ensures_item, format_expr, to_camel_case};
7
8/// Generate TypeScript skeleton code from a parsed intent file.
9pub fn generate(file: &ast::File) -> String {
10    let lang = Language::TypeScript;
11    let mut out = String::new();
12
13    // Header
14    out.push_str(&format!("// Generated from {}.intent\n", file.module.name));
15    if let Some(doc) = &file.doc {
16        out.push_str("/**\n");
17        for line in &doc.lines {
18            out.push_str(&format!(" * {line}\n"));
19        }
20        out.push_str(" */\n");
21    }
22    out.push('\n');
23
24    for item in &file.items {
25        match item {
26            ast::TopLevelItem::Entity(e) => generate_entity(&mut out, e, &lang),
27            ast::TopLevelItem::Action(a) => generate_action(&mut out, a, &lang),
28            ast::TopLevelItem::Invariant(inv) => generate_invariant(&mut out, inv),
29            ast::TopLevelItem::EdgeCases(ec) => generate_edge_cases(&mut out, ec),
30            ast::TopLevelItem::Test(_) => {}
31        }
32    }
33
34    out
35}
36
37fn generate_entity(out: &mut String, entity: &ast::EntityDecl, lang: &Language) {
38    // Doc comment
39    if let Some(doc) = &entity.doc {
40        out.push_str("/**\n");
41        for line in doc_text(doc).lines() {
42            out.push_str(&format!(" * {line}\n"));
43        }
44        out.push_str(" */\n");
45    }
46
47    out.push_str(&format!("export interface {} {{\n", entity.name));
48
49    for field in &entity.fields {
50        let ty = map_type(&field.ty, lang);
51        out.push_str(&format!("  {}: {};\n", to_camel_case(&field.name), ty));
52    }
53
54    out.push_str("}\n\n");
55}
56
57fn generate_action(out: &mut String, action: &ast::ActionDecl, lang: &Language) {
58    // JSDoc
59    out.push_str("/**\n");
60    if let Some(doc) = &action.doc {
61        for line in doc_text(doc).lines() {
62            out.push_str(&format!(" * {line}\n"));
63        }
64        out.push_str(" *\n");
65    }
66
67    // Parameters
68    for p in &action.params {
69        let ty = map_type(&p.ty, lang);
70        out.push_str(&format!(" * @param {} - {ty}\n", to_camel_case(&p.name)));
71    }
72
73    // Requires
74    if let Some(req) = &action.requires {
75        out.push_str(" *\n * @requires\n");
76        for cond in &req.conditions {
77            out.push_str(&format!(" *   - {}\n", format_expr(cond)));
78        }
79    }
80
81    // Ensures
82    if let Some(ens) = &action.ensures {
83        out.push_str(" *\n * @ensures\n");
84        for item in &ens.items {
85            out.push_str(&format!(" *   - {}\n", format_ensures_item(item)));
86        }
87    }
88
89    // Properties
90    if let Some(props) = &action.properties {
91        out.push_str(" *\n * @properties\n");
92        for entry in &props.entries {
93            out.push_str(&format!(
94                " *   - {}: {}\n",
95                entry.key,
96                crate::format_prop_value(&entry.value)
97            ));
98        }
99    }
100
101    out.push_str(" */\n");
102
103    // Function signature
104    let fn_name = to_camel_case(&action.name);
105    let params: Vec<String> = action
106        .params
107        .iter()
108        .map(|p| {
109            let ty = map_type(&p.ty, lang);
110            format!("{}: {ty}", to_camel_case(&p.name))
111        })
112        .collect();
113
114    out.push_str(&format!(
115        "export function {fn_name}({}): void {{\n",
116        params.join(", ")
117    ));
118    out.push_str(&format!(
119        "  throw new Error(\"TODO: implement {fn_name}\");\n"
120    ));
121    out.push_str("}\n\n");
122}
123
124fn generate_invariant(out: &mut String, inv: &ast::InvariantDecl) {
125    out.push_str(&format!("// Invariant: {}\n", inv.name));
126    if let Some(doc) = &inv.doc {
127        for line in doc_text(doc).lines() {
128            out.push_str(&format!("// {line}\n"));
129        }
130    }
131    out.push_str(&format!("// {}\n\n", format_expr(&inv.body)));
132}
133
134fn generate_edge_cases(out: &mut String, ec: &ast::EdgeCasesDecl) {
135    out.push_str("// Edge cases:\n");
136    for rule in &ec.rules {
137        out.push_str(&format!(
138            "// when {} => {}()\n",
139            format_expr(&rule.condition),
140            rule.action.name,
141        ));
142    }
143    out.push('\n');
144}