bashrs 6.66.0

Rust-to-Shell transpiler for deterministic bootstrap scripts
//! # SPRINT 2: ShellCheck Validation Tests
//!
//! This module validates that ALL generated shell scripts pass ShellCheck
//! with POSIX compliance (`shellcheck -s sh`).
//!
//! Following 現地現物 (Genchi Genbutsu) - Direct observation principle:
//! We test against REAL shell linters, not just our own assumptions.
//!
//! ## Critical Invariant
//! **Every generated script must pass `shellcheck -s sh`**

use crate::{transpile, Config};
use std::fs;
use std::process::Command;
use tempfile::TempDir;

/// Helper to run shellcheck on generated script
fn shellcheck_validate(shell_script: &str) -> Result<(), String> {
    let temp_dir = TempDir::new().unwrap();
    let script_path = temp_dir.path().join("test.sh");

    fs::write(&script_path, shell_script).unwrap();

    let output = Command::new("shellcheck")
        .arg("-s")
        .arg("sh") // POSIX shell
        .arg("--severity=error") // Only errors (not warnings)
        .arg(&script_path)
        .output()
        .expect("Failed to run shellcheck - is it installed?");

    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        Err(format!(
            "ShellCheck failed:\nSTDOUT:\n{}\nSTDERR:\n{}\n\nScript:\n{}",
            stdout, stderr, shell_script
        ))
    }
}

/// Helper to transpile and validate with shellcheck
fn transpile_and_validate(source: &str) -> Result<String, String> {
    let config = Config::default();
    let shell_script =
        transpile(source, &config).map_err(|e| format!("Transpilation failed: {}", e))?;

    shellcheck_validate(&shell_script)?;

    Ok(shell_script)
}

// ============================================================================
// PROPERTY 1: All basic constructs pass ShellCheck
// ============================================================================

#[test]
fn test_shellcheck_empty_main() {
    let source = r#"
        fn main() {}
    "#;

    transpile_and_validate(source).expect("Empty main should pass shellcheck");
}

#[test]
fn test_shellcheck_simple_variable() {
    let source = r#"
        fn main() {
            let x = "hello";
        }
    "#;

    transpile_and_validate(source).expect("Simple variable should pass shellcheck");
}

#[test]
fn test_shellcheck_echo_command() {
    let source = r#"
        fn main() {
            echo("Hello, World!");
        }
    "#;

    transpile_and_validate(source).expect("Echo command should pass shellcheck");
}

#[test]
fn test_shellcheck_if_statement() {
    let source = r#"
        fn main() {
            let condition = true;
            if condition {
                echo("true branch");
            } else {
                echo("false branch");
            }
        }
    "#;

    transpile_and_validate(source).expect("If statement should pass shellcheck");
}

#[test]
fn test_shellcheck_nested_if() {
    let source = r#"
        fn main() {
            let a = true;
            let b = false;

            if a {
                if b {
                    echo("both true");
                } else {
                    echo("a true, b false");
                }
            } else {
                echo("a false");
            }
        }
    "#;

    transpile_and_validate(source).expect("Nested if should pass shellcheck");
}

#[test]
fn test_shellcheck_multiple_variables() {
    let source = r#"
        fn main() {
            let name = "Rash";
            let version = "0.3.3";
            let greeting = "Hello";

            echo(name);
            echo(version);
            echo(greeting);
        }
    "#;

    transpile_and_validate(source).expect("Multiple variables should pass shellcheck");
}

// ============================================================================
// PROPERTY 2: User-defined functions pass ShellCheck
// ============================================================================

#[test]
fn test_shellcheck_user_function() {
    let source = r#"
        fn main() {
            greet("World");
        }

        fn greet(name: &str) {
            echo(name);
        }
    "#;

    transpile_and_validate(source).expect("User function should pass shellcheck");
}

#[test]
fn test_shellcheck_multiple_functions() {
    let source = r#"
        fn main() {
            setup();
            process();
            cleanup();
        }

        fn setup() {
            echo("Setting up...");
        }

        fn process() {
            echo("Processing...");
        }

        fn cleanup() {
            echo("Cleaning up...");
        }
    "#;

    transpile_and_validate(source).expect("Multiple functions should pass shellcheck");
}

#[test]
fn test_shellcheck_function_with_params() {
    let source = r#"
        fn main() {
            install("package", "/usr/local");
        }

        fn install(name: &str, path: &str) {
            echo(name);
            echo(path);
        }
    "#;

    transpile_and_validate(source).expect("Function with params should pass shellcheck");
}

// ============================================================================
// PROPERTY 3: Unicode strings pass ShellCheck
// ============================================================================

#[test]
fn test_shellcheck_unicode_emoji() {
    let source = r#"
        fn main() {
            let msg = "Hello 👋 World 🦀";
            echo(msg);
        }
    "#;

    transpile_and_validate(source).expect("Unicode emoji should pass shellcheck");
}

#[test]
fn test_shellcheck_unicode_cjk() {
    let source = r#"
        fn main() {
            let msg = "你好世界";
            echo(msg);
        }
    "#;

    transpile_and_validate(source).expect("Unicode CJK should pass shellcheck");
}

#[test]
fn test_shellcheck_unicode_arabic() {
    let source = r#"
        fn main() {
            let msg = "مرحبا";
            echo(msg);
        }
    "#;

    transpile_and_validate(source).expect("Unicode Arabic should pass shellcheck");
}

// ============================================================================
// PROPERTY 4: Special characters pass ShellCheck
// ============================================================================

#[test]
fn test_shellcheck_special_chars_in_strings() {
    let source = r#"
        fn main() {
            let msg1 = "String with spaces";
            let msg2 = "String with 'quotes'";
            let msg3 = "String with $dollar";
            let msg4 = "String with |pipe|";

            echo(msg1);
            echo(msg2);
            echo(msg3);
            echo(msg4);
        }
    "#;

    transpile_and_validate(source).expect("Special characters should pass shellcheck");
}

// Note: Backticks in string literals trigger SC2006 validation error
// This is the validation framework working correctly!
// Backticks should not be used in generated shell scripts.

#[test]
fn test_shellcheck_newlines_tabs() {
    let source = r#"
        fn main() {
            let msg = "Line1\nLine2\tTabbed";
            echo(msg);
        }
    "#;

    transpile_and_validate(source).expect("Newlines and tabs should pass shellcheck");
}

// ============================================================================
// PROPERTY 5: Empty branches pass ShellCheck
// ============================================================================

#[test]
fn test_shellcheck_empty_if_branch() {
    let source = r#"
        fn main() {
            let condition = true;

            if condition {
                // Empty
            } else {
                echo("else branch");
            }
        }
    "#;

    transpile_and_validate(source).expect("Empty if branch should pass shellcheck");
}

#[test]
fn test_shellcheck_empty_else_branch() {
    let source = r#"
        fn main() {
            let condition = false;

            if condition {
                echo("if branch");
            } else {
                // Empty
            }
        }
    "#;

    transpile_and_validate(source).expect("Empty else branch should pass shellcheck");
}

#[test]
fn test_shellcheck_both_branches_empty() {
    let source = r#"
        fn main() {
            let condition = true;

            if condition {
                // Empty
            } else {
                // Empty
            }
        }
    "#;

    transpile_and_validate(source).expect("Both empty branches should pass shellcheck");
}

// ============================================================================
// PROPERTY 6: Variable shadowing passes ShellCheck
// ============================================================================

#[test]
fn test_shellcheck_variable_shadowing() {
    let source = r#"
        fn main() {
            let x = "outer";
            echo(x);

            if true {
                let x = "inner";
                echo(x);
            }

            echo(x);
        }
    "#;

    transpile_and_validate(source).expect("Variable shadowing should pass shellcheck");
}

// ============================================================================
// PROPERTY 7: Long variable names pass ShellCheck
// ============================================================================

#[test]

include!("shellcheck_validation_tests_main.rs");