///|
/// Pretty Printer / Emitter — P0-006.
/// Converts MoonBit AST (P0-005) into formatted source code.
/// Pure mechanical transformation — no sqlc/IR/database semantics.
//
// Copyright 2026 Mairzzcllo
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
struct Emitter {
buf : Array[String]
}
///|
fn Emitter::new() -> Emitter {
{ buf: [] }
}
///|
fn Emitter::push(self : Emitter, s : String) -> Unit {
self.buf.push(s)
}
///|
fn push_indent(em : Emitter, indent : Int) -> Unit {
let mut i = 0
while i < indent {
em.push(" ")
i = i + 1
}
}
///|
fn push_line(em : Emitter, indent : Int, s : String) -> Unit {
push_indent(em, indent)
em.push(s)
em.push("\n")
}
///|
fn Emitter::finish(self : Emitter) -> String {
let mut result = ""
for s in self.buf {
result = result + s
}
result
}
///|
/// Emit a package declaration line. Defaults to "main" when name is empty or whitespace-only.
pub fn emit_package_declaration(name : String) -> String {
let trimmed = name.trim()
let pkg = if trimmed.length() > 0 { trimmed.to_owned() } else { "main" }
let em = Emitter::new()
em.push("package ")
em.push(pkg)
em.push("\n\n")
em.finish()
}
///|
/// Emit a SourceFile to a formatted MoonBit source string.
pub fn emit_source_file(file : SourceFile) -> String {
let em = Emitter::new()
let mut first = true
for item in file.items {
if !first {
em.push("\n")
}
emit_top_level(em, 0, item)
first = false
}
em.finish()
}
///|
fn emit_top_level(em : Emitter, indent : Int, item : TopLevel) -> Unit {
match item {
Import(i) => emit_import(em, indent, i)
Struct(s) => emit_struct_def(em, indent, s)
Enum(e) => emit_enum_def(em, indent, e)
Fn(f) => emit_fn_def(em, indent, f)
TypeAlias(t) => emit_type_alias(em, indent, t)
}
}
///|
fn emit_doc_comment(em : Emitter, indent : Int, doc : String?) -> Unit {
match doc {
Some(text) =>
for line in text.split("\n") {
push_line(em, indent, "/// " + line.to_owned())
}
None => ()
}
}
///|
fn emit_import(em : Emitter, indent : Int, i : ImportDef) -> Unit {
let mut s = "import \"" + i.path + "\""
match i.import_alias {
Some(a) => s = s + " @" + a
None => ()
}
push_line(em, indent, s)
}
///|
fn emit_struct_def(em : Emitter, indent : Int, s : StructDef) -> Unit {
emit_doc_comment(em, indent, s.doc)
let mut header = ""
if s.is_pub {
header = "pub "
}
header = header + "struct " + s.name
if s.fields.length() == 0 {
push_line(em, indent, header)
return
}
push_line(em, indent, header + " {")
for f in s.fields {
emit_field_def(em, indent + 1, f)
}
push_line(em, indent, "}")
}
///|
fn emit_field_def(em : Emitter, indent : Int, f : FieldDef) -> Unit {
emit_doc_comment(em, indent, f.doc)
push_indent(em, indent)
// Emit @json.tag("name") annotation before field name
match f.json_tag {
Some(tag) => em.push("@json.tag(\"" + escape_string(tag) + "\") ")
None => ()
}
// Emit @db.tag("name") annotation before field name
match f.db_tag {
Some(tag) => em.push("@db.tag(\"" + escape_string(tag) + "\") ")
None => ()
}
em.push(f.name)
em.push(": ")
em.push(emit_type_expr(f.ty))
em.push("\n")
}
///|
fn emit_enum_def(em : Emitter, indent : Int, e : EnumDef) -> Unit {
emit_doc_comment(em, indent, e.doc)
let mut header = ""
if e.is_pub {
header = "pub "
}
header = header + "enum " + e.name
if e.variants.length() == 0 {
push_line(em, indent, header)
return
}
push_line(em, indent, header + " {")
for v in e.variants {
emit_variant_def(em, indent + 1, v)
}
push_line(em, indent, "}")
}
///|
fn emit_variant_def(em : Emitter, indent : Int, v : VariantDef) -> Unit {
emit_doc_comment(em, indent, v.doc)
push_line(em, indent, v.name)
}
///|
fn emit_fn_def(em : Emitter, indent : Int, f : FnDef) -> Unit {
emit_doc_comment(em, indent, f.doc)
let mut header = ""
if f.is_pub {
header = "pub "
}
header = header + "fn " + f.name + "("
let mut first = true
for p in f.params {
if !first {
header = header + ", "
}
header = header + p.name + ": " + emit_type_expr(p.ty)
first = false
}
header = header + ")"
let ret = emit_type_expr(f.return_ty)
if ret != "Unit" {
header = header + " -> " + ret
}
if f.body.length() == 0 {
push_line(em, indent, header)
return
}
push_line(em, indent, header + " {")
for expr in f.body {
emit_expr(em, indent + 1, expr)
}
push_line(em, indent, "}")
}
///|
fn emit_type_alias(em : Emitter, indent : Int, t : TypeAliasDef) -> Unit {
emit_doc_comment(em, indent, t.doc)
push_line(em, indent, "type " + t.name + " = " + emit_type_expr(t.underlying))
}
///|
fn emit_type_expr(t : TypeExpr) -> String {
match t {
Named(s) => s
Parameterized(name, args) => {
let mut s = name + "["
let mut first = true
for a in args {
if !first {
s = s + ", "
}
s = s + emit_type_expr(a)
first = false
}
s + "]"
}
Option(inner) => "Option[" + emit_type_expr(inner) + "]"
Wildcard => "_"
}
}
///|
/// Escape special characters in string literals for MoonBit source output.
fn escape_string(s : String) -> String {
let mut result = ""
let mut i = 0
while i < s.length() {
let ch = s[i]
if ch == '"' {
result = result + "\\\""
} else if ch == '\n' {
result = result + "\\n"
} else if ch == '\t' {
result = result + "\\t"
} else if ch == '\r' {
result = result + "\\r"
} else if ch == '\\' {
result = result + "\\\\"
} else {
result = result + s[i:i + 1].to_owned()
}
i = i + 1
}
result
}
///|
/// Emit arms of a match expression.
/// Nested match bodies are emitted inline to keep formatting compact.
/// Block bodies are emitted with full multi-line formatting.
fn emit_match_arms(
em : Emitter,
arm_indent : Int,
body_indent : Int,
arms : Array[(String, Expr)],
) -> Unit {
let mut i = 0
while i < arms.length() {
let arm = arms[i]
let pattern = arm.0
let body = arm.1
push_indent(em, arm_indent)
match body {
Match(_, _) => {
em.push(pattern + " => ")
emit_match_inline(em, body_indent, body)
em.push("\n")
}
Block(exprs) => {
em.push(pattern + " => {\n")
for e in exprs {
emit_expr(em, body_indent + 1, e)
}
push_indent(em, body_indent)
em.push("}\n")
}
_ => em.push(pattern + " => " + expr_to_string(body) + "\n")
}
i = i + 1
}
}
///|
/// Emit a match expression inline (no leading newline/indent).
/// Used for nested match arms to keep compact formatting.
fn emit_match_inline(em : Emitter, indent : Int, m : Expr) -> Unit {
match m {
Match(scrutinee, arms) => {
em.push("match " + expr_to_string(scrutinee) + " {\n")
emit_match_arms(em, indent + 1, indent + 1, arms)
push_indent(em, indent)
em.push("}")
}
_ => em.push(expr_to_string(m))
}
}
///|
fn emit_expr(em : Emitter, indent : Int, expr : Expr) -> Unit {
match expr {
Ident(s) => push_line(em, indent, s)
Call(name, args) => {
let mut s = name + "("
let mut first = true
for a in args {
if !first {
s = s + ", "
}
s = s + expr_to_string(a)
first = false
}
s = s + ")"
push_line(em, indent, s)
}
Let(name, val) => {
push_indent(em, indent)
em.push("let " + name + " = " + expr_to_string(val) + "\n")
}
StrLit(s) => push_line(em, indent, "\"" + escape_string(s) + "\"")
IntLit(n) => push_line(em, indent, n.to_string())
ArrLit(args) => {
let mut s = "["
let mut first = true
for a in args {
if !first {
s = s + ", "
}
s = s + expr_to_string(a)
first = false
}
s = s + "]"
push_line(em, indent, s)
}
Index(target, idx) =>
push_line(
em,
indent,
expr_to_string(target) + "[" + expr_to_string(idx) + "]",
)
Lambda(params, body) => {
let mut s = "fn("
let mut first = true
for p in params {
if !first {
s = s + ", "
}
s = s + p.name + ": " + emit_type_expr(p.ty)
first = false
}
s = s + ") { " + expr_to_string(body) + " }"
push_line(em, indent, s)
}
BinOp(op, left, right) =>
push_line(
em,
indent,
expr_to_string(left) + " " + op + " " + expr_to_string(right),
)
If(cond, then_expr, else_expr) => {
push_indent(em, indent)
em.push("if " + expr_to_string(cond) + " {\n")
emit_expr(em, indent + 1, then_expr)
push_indent(em, indent)
em.push("} else {\n")
emit_expr(em, indent + 1, else_expr)
push_indent(em, indent)
em.push("}\n")
}
Return(val) => push_line(em, indent, "return " + expr_to_string(val))
ForLoop(varname, iter, body) => {
push_indent(em, indent)
em.push("for " + varname + " in " + expr_to_string(iter) + " {\n")
for stmt in body {
emit_expr(em, indent + 1, stmt)
}
push_indent(em, indent)
em.push("}\n")
}
Match(scrutinee, arms) => {
push_indent(em, indent)
em.push("match " + expr_to_string(scrutinee) + " {\n")
emit_match_arms(em, indent + 1, indent + 1, arms)
push_indent(em, indent)
em.push("}\n")
}
Unit => push_line(em, indent, "()")
Block(exprs) => {
push_line(em, indent, "{")
for e in exprs {
emit_expr(em, indent + 1, e)
}
push_line(em, indent, "}")
}
}
}
///|
fn expr_to_string(e : Expr) -> String {
match e {
Ident(s) => s
Call(name, args) => {
let mut s = name + "("
let mut first = true
for a in args {
if !first {
s = s + ", "
}
s = s + expr_to_string(a)
first = false
}
s + ")"
}
Let(name, val) => "let " + name + " = " + expr_to_string(val)
StrLit(s) => "\"" + escape_string(s) + "\""
IntLit(n) => n.to_string()
ArrLit(args) => {
let mut s = "["
let mut first = true
for a in args {
if !first {
s = s + ", "
}
s = s + expr_to_string(a)
first = false
}
s + "]"
}
Index(target, idx) =>
expr_to_string(target) + "[" + expr_to_string(idx) + "]"
Unit => "()"
Lambda(params, body) => {
let mut s = "fn("
let mut first = true
for p in params {
if !first {
s = s + ", "
}
s = s + p.name + ": " + emit_type_expr(p.ty)
first = false
}
s + ") { " + expr_to_string(body) + " }"
}
BinOp(op, left, right) =>
expr_to_string(left) + " " + op + " " + expr_to_string(right)
If(cond, then_expr, else_expr) =>
"if " +
expr_to_string(cond) +
" { " +
expr_to_string(then_expr) +
" } else { " +
expr_to_string(else_expr) +
" }"
Return(val) => "return " + expr_to_string(val)
ForLoop(varname, iter, body) => {
let mut s = "for " + varname + " in " + expr_to_string(iter) + " {\n"
for stmt in body {
s = s + " " + expr_to_string(stmt) + "\n"
}
s + "}"
}
Match(scrutinee, arms) => {
let mut s = "match " + expr_to_string(scrutinee) + " { "
let mut i = 0
while i < arms.length() {
let arm = arms[i]
let pattern = arm.0
let body = arm.1
s = s + pattern + " => " + expr_to_string(body) + "; "
i = i + 1
}
s + "}"
}
Block(exprs) => {
let mut s = "{\n"
for e in exprs {
s = s + " " + expr_to_string(e) + "\n"
}
s + "}"
}
}
}
// === Tests ===
///|
test "emit: empty source file" {
let f : SourceFile = { items: [] }
let result = emit_source_file(f)
inspect(result, content="")
}
///|
test "emit: import without alias" {
let f : SourceFile = {
items: [Import({ path: "moonbitlang/core/builtin", import_alias: None })],
}
let result = emit_source_file(f)
inspect(result, content="import \"moonbitlang/core/builtin\"\n")
}
///|
test "emit: import with alias" {
let f : SourceFile = {
items: [
Import({
path: "moonbitlang/core/encoding/utf8",
import_alias: Some("utf8"),
}),
],
}
let result = emit_source_file(f)
inspect(result, content="import \"moonbitlang/core/encoding/utf8\" @utf8\n")
}
///|
test "emit: public struct with fields" {
let s : StructDef = {
name: "User",
fields: [
{
name: "id",
col_name: "id",
ty: Named("Int"),
doc: None,
json_tag: None,
db_tag: None,
},
{
name: "email",
col_name: "email",
ty: Named("String"),
doc: Some("user email"),
json_tag: None,
db_tag: None,
},
],
doc: Some("A user record"),
is_pub: true,
}
let f : SourceFile = { items: [Struct(s)] }
let result = emit_source_file(f)
inspect(
result,
content="/// A user record\npub struct User {\n id: Int\n /// user email\n email: String\n}\n",
)
}
///|
test "emit: private struct without fields" {
let s : StructDef = { name: "Empty", fields: [], doc: None, is_pub: false }
let f : SourceFile = { items: [Struct(s)] }
let result = emit_source_file(f)
inspect(result, content="struct Empty\n")
}
///|
test "emit: pub enum with variants" {
let e : EnumDef = {
name: "Color",
variants: [{ name: "Red", doc: None }, { name: "Blue", doc: None }],
doc: None,
is_pub: true,
}
let f : SourceFile = { items: [Enum(e)] }
let result = emit_source_file(f)
inspect(result, content="pub enum Color {\n Red\n Blue\n}\n")
}
///|
test "emit: enum without variants" {
let e : EnumDef = { name: "Empty", variants: [], doc: None, is_pub: false }
let f : SourceFile = { items: [Enum(e)] }
let result = emit_source_file(f)
inspect(result, content="enum Empty\n")
}
///|
test "emit: function with parameters and body" {
let f : FnDef = {
name: "greet",
params: [{ name: "name", ty: Named("String") }],
return_ty: Named("String"),
body: [
Let("result", Call("format", [StrLit("Hello, "), Ident("name")])),
Ident("result"),
],
doc: Some("Greet someone"),
is_pub: true,
}
let sf : SourceFile = { items: [Fn(f)] }
let result = emit_source_file(sf)
inspect(
result,
content="/// Greet someone\npub fn greet(name: String) -> String {\n let result = format(\"Hello, \", name)\n result\n}\n",
)
}
///|
test "emit: function without return type (Unit)" {
let f : FnDef = {
name: "noop",
params: [],
return_ty: Named("Unit"),
body: [Unit],
doc: None,
is_pub: false,
}
let sf : SourceFile = { items: [Fn(f)] }
let result = emit_source_file(sf)
inspect(result, content="fn noop() {\n ()\n}\n")
}
///|
test "emit: function without body" {
let f : FnDef = {
name: "placeholder",
params: [],
return_ty: Named("Int"),
body: [],
doc: None,
is_pub: false,
}
let sf : SourceFile = { items: [Fn(f)] }
let result = emit_source_file(sf)
inspect(result, content="fn placeholder() -> Int\n")
}
///|
test "emit: type alias" {
let t : TypeAliasDef = {
name: "Json",
underlying: Named("String"),
doc: None,
}
let sf : SourceFile = { items: [TypeAlias(t)] }
let result = emit_source_file(sf)
inspect(result, content="type Json = String\n")
}
///|
test "emit: type alias with doc" {
let t : TypeAliasDef = {
name: "Json",
underlying: Named("String"),
doc: Some("JSON encoded as string"),
}
let sf : SourceFile = { items: [TypeAlias(t)] }
let result = emit_source_file(sf)
inspect(result, content="/// JSON encoded as string\ntype Json = String\n")
}
///|
test "emit: parameterized type expr" {
let t : TypeExpr = Parameterized("Array", [Named("Int")])
let s = emit_type_expr(t)
inspect(s, content="Array[Int]")
}
///|
test "emit: nested parameterized type expr" {
let t : TypeExpr = Parameterized("Map", [Named("String"), Named("Int")])
let s = emit_type_expr(t)
inspect(s, content="Map[String, Int]")
}
///|
test "emit: option type expr" {
let t : TypeExpr = Option(Named("String"))
let s = emit_type_expr(t)
inspect(s, content="Option[String]")
}
///|
test "emit: wildcard type expr" {
let t : TypeExpr = Wildcard
let s = emit_type_expr(t)
inspect(s, content="_")
}
///|
test "emit: lambda expression" {
let body : Expr = Ident("x")
let lambda : Expr = Lambda([{ name: "row", ty: Named("Row") }], body)
let em = Emitter::new()
emit_expr(em, 0, lambda)
let result = em.finish()
inspect(result, content="fn(row: Row) { x }\n")
}
///|
test "emit: block expression" {
let block : Expr = Block([Let("x", StrLit("hello")), Ident("x")])
let em = Emitter::new()
emit_expr(em, 0, block)
let result = em.finish()
inspect(result, content="{\n let x = \"hello\"\n x\n}\n")
}
///|
test "emit: full source file with multiple items" {
let imp : ImportDef = { path: "moonbitlang/core/builtin", import_alias: None }
let s : StructDef = {
name: "Point",
fields: [
{
name: "x",
col_name: "x",
ty: Named("Int"),
doc: None,
json_tag: None,
db_tag: None,
},
{
name: "y",
col_name: "y",
ty: Named("Int"),
doc: None,
json_tag: None,
db_tag: None,
},
],
doc: None,
is_pub: true,
}
let f : FnDef = {
name: "origin",
params: [],
return_ty: Named("Point"),
body: [Call("Point", [StrLit("0"), StrLit("0")])],
doc: None,
is_pub: true,
}
let sf : SourceFile = { items: [Import(imp), Struct(s), Fn(f)] }
let result = emit_source_file(sf)
inspect(
result,
content="import \"moonbitlang/core/builtin\"\n\npub struct Point {\n x: Int\n y: Int\n}\n\npub fn origin() -> Point {\n Point(\"0\", \"0\")\n}\n",
)
}
///|
test "emit: string literal expr" {
let s : Expr = StrLit("SELECT * FROM users")
let em = Emitter::new()
emit_expr(em, 0, s)
let result = em.finish()
inspect(result, content="\"SELECT * FROM users\"\n")
}
///|
test "emit: index expression" {
let e : Expr = Index(Ident("arr"), IntLit(0))
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="arr[0]\n")
}
///|
test "emit: binop expression" {
let e : Expr = BinOp(">", Ident("a"), IntLit(5))
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="a > 5\n")
}
///|
test "emit: if expression" {
let e : Expr = If(BinOp(">", Ident("x"), IntLit(0)), Ident("x"), Ident("neg"))
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="if x > 0 {\n x\n} else {\n neg\n}\n")
}
///|
test "emit: call expression with multiple args" {
let call : Expr = Call("db.query_many", [Ident("db"), Ident("sql")])
let em = Emitter::new()
emit_expr(em, 0, call)
let result = em.finish()
inspect(result, content="db.query_many(db, sql)\n")
}
///|
test "emit: lambda with typed params" {
let body : Expr = Call("User::decode", [Ident("row")])
let lambda : Expr = Lambda([{ name: "row", ty: Named("Row") }], body)
let em = Emitter::new()
emit_expr(em, 0, lambda)
let result = em.finish()
inspect(result, content="fn(row: Row) { User::decode(row) }\n")
}
///|
test "emit: struct with doc comment and field doc comments" {
let s : StructDef = {
name: "User",
fields: [
{
name: "id",
col_name: "id",
ty: Named("Int"),
doc: Some("primary key"),
json_tag: None,
db_tag: None,
},
{
name: "name",
col_name: "name",
ty: Named("String"),
doc: Some("full name"),
json_tag: None,
db_tag: None,
},
],
doc: Some("Represents a user in the system"),
is_pub: true,
}
let sf : SourceFile = { items: [Struct(s)] }
let result = emit_source_file(sf)
inspect(
result,
content="/// Represents a user in the system\npub struct User {\n /// primary key\n id: Int\n /// full name\n name: String\n}\n",
)
}
///|
test "emit: function with parameterized return type" {
let f : FnDef = {
name: "get_user",
params: [{ name: "id", ty: Named("Int") }],
return_ty: Parameterized("Option", [Named("User")]),
body: [Ident("None")],
doc: None,
is_pub: true,
}
let sf : SourceFile = { items: [Fn(f)] }
let result = emit_source_file(sf)
inspect(
result,
content="pub fn get_user(id: Int) -> Option[User] {\n None\n}\n",
)
}
///|
test "emit: array literal expr" {
let e : Expr = ArrLit([StrLit("hello"), StrLit("world")])
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="[\"hello\", \"world\"]\n")
}
///|
test "emit: empty array literal expr" {
let e : Expr = ArrLit([])
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="[]\n")
}
///|
test "escape_string: dollar sign is not escaped" {
let result = escape_string("$")
inspect(result, content="$")
}
///|
test "escape_string: multiple dollar signs pass through" {
let result = escape_string("$$x$y$")
inspect(result, content="$$x$y$")
}
///|
test "escape_string: dollar sign at start passes through" {
let result = escape_string("$hello")
inspect(result, content="$hello")
}
///|
test "escape_string: double quotes are escaped" {
let result = escape_string("hello\"world")
inspect(result, content="hello\\\"world")
}
///|
test "escape_string: newline and tab are escaped" {
let result = escape_string("line1\nline2\tcol")
inspect(result, content="line1\\nline2\\tcol")
}
///|
test "escape_string: backslash is escaped, dollar passes through" {
let result = escape_string("a\\b$c")
inspect(result, content="a\\\\b$c")
}
///|
test "emit: strlit with double quotes emits escaped" {
let e : Expr = StrLit("SELECT \"id\" FROM users")
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="\"SELECT \\\"id\\\" FROM users\"\n")
}
///|
test "emit: strlit with newline emits escaped" {
let e : Expr = StrLit("multi\nline")
let em = Emitter::new()
emit_expr(em, 0, e)
let result = em.finish()
inspect(result, content="\"multi\\nline\"\n")
}
///|
test "emit: field with json_tag outputs @json.tag annotation" {
let s : StructDef = {
name: "Tagged",
fields: [
{
name: "user_id",
col_name: "user_id",
ty: Named("Int64"),
doc: None,
json_tag: Some("user_id"),
db_tag: None,
},
{
name: "email_addr",
col_name: "email_addr",
ty: Named("String"),
doc: None,
json_tag: None,
db_tag: Some("email_addr"),
},
],
doc: None,
is_pub: true,
}
let sf : SourceFile = { items: [Struct(s)] }
let result = emit_source_file(sf)
inspect(
result,
content="pub struct Tagged {\n @json.tag(\"user_id\") user_id: Int64\n @db.tag(\"email_addr\") email_addr: String\n}\n",
)
}
///|
test "emit: field with both json_tag and db_tag outputs both annotations" {
let s : StructDef = {
name: "Both",
fields: [
{
name: "full_name",
col_name: "full_name",
ty: Named("String"),
doc: None,
json_tag: Some("full_name"),
db_tag: Some("full_name"),
},
],
doc: None,
is_pub: true,
}
let sf : SourceFile = { items: [Struct(s)] }
let result = emit_source_file(sf)
inspect(
result,
content="pub struct Both {\n @json.tag(\"full_name\") @db.tag(\"full_name\") full_name: String\n}\n",
)
}
///|
test "emit: field with no tags outputs no annotations" {
let s : StructDef = {
name: "Plain",
fields: [
{
name: "id",
col_name: "id",
ty: Named("Int64"),
doc: None,
json_tag: None,
db_tag: None,
},
],
doc: None,
is_pub: true,
}
let sf : SourceFile = { items: [Struct(s)] }
let result = emit_source_file(sf)
inspect(result, content="pub struct Plain {\n id: Int64\n}\n")
}
///|
test "emit_package_declaration: non-empty name produces package line" {
let result = emit_package_declaration("myapp")
inspect(result, content="package myapp\n\n")
}
///|
test "emit_package_declaration: empty name defaults to main" {
let result = emit_package_declaration("")
inspect(result, content="package main\n\n")
}
///|
test "emit_package_declaration: whitespace-only defaults to main" {
let result = emit_package_declaration(" ")
inspect(result, content="package main\n\n")
}
///|
/// P1-045: emit_package_declaration trims surrounding whitespace from package name.
test "emit_package_declaration: trims surrounding whitespace" {
let result = emit_package_declaration(" myapp ")
inspect(result, content="package myapp\n\n")
}