diff --git a/app/src/core/transpile/emit/mod.rs b/app/src/core/transpile/emit/mod.rs index 748a5d4..015c0c5 100644 --- a/app/src/core/transpile/emit/mod.rs +++ b/app/src/core/transpile/emit/mod.rs @@ -201,7 +201,7 @@ fn emit_bash_items(out: &mut String, program: &Program) { Item::Function { name, body } => { out.push_str(name); out.push_str("() {\n"); - emit_bash_statements(out, body, 1); + emit_bash_body(out, body, 1); out.push_str("}\n\n"); } Item::Statement { statement } => emit_bash_statement(out, statement, 0), @@ -209,6 +209,15 @@ fn emit_bash_items(out: &mut String, program: &Program) { } } +fn emit_bash_body(out: &mut String, statements: &[Statement], indent: usize) { + if statements.is_empty() { + let pad = " ".repeat(indent); + out.push_str(&format!("{pad}:\n")); + } else { + emit_bash_statements(out, statements, indent); + } +} + fn emit_bash_statements(out: &mut String, statements: &[Statement], indent: usize) { for statement in statements { emit_bash_statement(out, statement, indent); @@ -300,23 +309,23 @@ fn emit_bash_statement(out: &mut String, statement: &Statement, indent: usize) { else_body, } => { out.push_str(&format!("{pad}if {}; then\n", bash_predicate(predicate))); - emit_bash_statements(out, then_body, indent + 1); + emit_bash_body(out, then_body, indent + 1); if !else_body.is_empty() { out.push_str(&format!("{pad}else\n")); - emit_bash_statements(out, else_body, indent + 1); + emit_bash_body(out, else_body, indent + 1); } out.push_str(&format!("{pad}fi\n")); } Statement::While { predicate, body } => { out.push_str(&format!("{pad}while {}; do\n", bash_predicate(predicate))); - emit_bash_statements(out, body, indent + 1); + emit_bash_body(out, body, indent + 1); out.push_str(&format!("{pad}done\n")); } Statement::Case { value, arms } => { out.push_str(&format!("{pad}case {} in\n", bash_value(value))); for arm in arms { out.push_str(&format!("{pad} {})\n", arm.patterns.join("|"))); - emit_bash_statements(out, &arm.body, indent + 2); + emit_bash_body(out, &arm.body, indent + 2); out.push_str(&format!("{pad} ;;\n")); } out.push_str(&format!("{pad}esac\n")); diff --git a/app/src/core/transpile/emit/powershell.rs b/app/src/core/transpile/emit/powershell.rs index 2499d71..19fecfd 100644 --- a/app/src/core/transpile/emit/powershell.rs +++ b/app/src/core/transpile/emit/powershell.rs @@ -342,6 +342,9 @@ fn powershell_value(value: &Value) -> String { ) } Value::Concat { parts } => { + if parts.is_empty() { + return "''".to_string(); + } let value = parts .iter() .map(powershell_value) diff --git a/app/tests/transpile.rs b/app/tests/transpile.rs index f5b19d2..a897777 100644 --- a/app/tests/transpile.rs +++ b/app/tests/transpile.rs @@ -40,6 +40,13 @@ fn run_transpile(fx: &Fixture, input_lang: &str, output_lang: &str) -> std::proc .expect("runseal should run") } +fn repo_root() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("app dir should have repo parent") + .to_path_buf() +} + fn sample_source() -> &'static str { r#" channel=${RUNSEAL_CHANNEL:-stable} @@ -336,6 +343,52 @@ fn bash_syntax_valid() { syntax::assert_bash(&stdout); } +#[test] +fn repo_wrapper_syntax() { + let root = repo_root(); + for wrapper in [ + ".runseal/wrappers/cloudflare.seal", + ".runseal/wrappers/init.seal", + ".runseal/wrappers/pr.seal", + ".runseal/wrappers/release.seal", + ] { + let source = root.join(wrapper); + + let bash = bin() + .current_dir(&root) + .arg("@transpile") + .arg("--input-lang=seal") + .arg("--output-lang=bash") + .arg(&source) + .output() + .expect("runseal should run"); + assert!( + bash.status.success(), + "{wrapper} bash stderr: {}", + String::from_utf8_lossy(&bash.stderr) + ); + let bash = String::from_utf8(bash.stdout).expect("bash output should be UTF-8"); + syntax::assert_bash(&bash); + + let powershell = bin() + .current_dir(&root) + .arg("@transpile") + .arg("--input-lang=seal") + .arg("--output-lang=powershell") + .arg(&source) + .output() + .expect("runseal should run"); + assert!( + powershell.status.success(), + "{wrapper} powershell stderr: {}", + String::from_utf8_lossy(&powershell.stderr) + ); + let powershell = + String::from_utf8(powershell.stdout).expect("powershell output should be UTF-8"); + syntax::assert_pwsh(&powershell); + } +} + #[test] fn powershell_readable() { let fx = fixture(sample_source()); @@ -352,6 +405,18 @@ fn powershell_readable() { syntax::assert_pwsh(&stdout); } +#[test] +fn empty_string_powershell() { + let fx = fixture("print \"\"\n"); + + let output = run_transpile(&fx, "seal", "powershell"); + + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).expect("stdout should be UTF-8"); + assert!(stdout.contains("Write-Output ''")); + syntax::assert_pwsh(&stdout); +} + #[test] fn sealir_to_seal() { let fx = fixture(sample_source());