bashrs 6.66.0

Rust-to-Shell transpiler for deterministic bootstrap scripts
#![allow(clippy::unwrap_used)]
#![allow(unused_imports)]

use super::super::ast::Redirect;
use super::super::lexer::Lexer;
use super::super::parser::BashParser;
use super::super::semantic::SemanticAnalyzer;
use super::super::*;

/// Helper: assert that BashParser handles the input without panicking.
/// Accepts both successful parses and parse errors (documentation tests
/// only verify the parser doesn't crash, not that the input is valid).

#[test]
fn test_PIPE_001_bash_vs_posix_pipes() {
    // DOCUMENTATION: Bash vs POSIX pipeline features
    //
    // Feature                  | POSIX sh           | Bash extensions
    // -------------------------|-------------------|------------------
    // Basic pipe (|)           | ✅ Supported       | ✅ Supported
    // Multi-stage (a|b|c)      | ✅ Supported       | ✅ Supported
    // Exit status ($?)         | ✅ Rightmost cmd   | ✅ Rightmost cmd
    // PIPESTATUS array         | ❌ Not available   | ✅ ${PIPESTATUS[@]}
    // pipefail option          | ❌ Not available   | ✅ set -o pipefail
    // lastpipe option          | ❌ Not available   | ✅ shopt -s lastpipe
    // |&  (pipe stderr too)    | ❌ Not available   | ✅ Bash 4.0+
    // Process substitution     | ❌ Not available   | ✅ <(cmd) >(cmd)
    //
    // bashrs policy:
    // - Support POSIX pipes (|) fully
    // - NOT SUPPORTED: PIPESTATUS, pipefail, lastpipe, |&, process substitution
    // - Generate POSIX-compliant pipelines only

    let posix_pipe = r#"cat file.txt | grep "pattern" | wc -l"#;
    let bash_pipestatus = r#"cat file.txt | grep "pattern"; echo ${PIPESTATUS[@]}"#;

    // POSIX pipe - SUPPORTED
    let posix_result = BashParser::new(posix_pipe);
    assert!(posix_result.is_ok(), "POSIX pipe should parse");

    // Bash PIPESTATUS - NOT SUPPORTED (Bash extension)
    let bash_result = BashParser::new(bash_pipestatus);
    match bash_result {
        Ok(mut parser) => {
            let _ = parser.parse();
            // PIPESTATUS is Bash extension, may or may not parse
        }
        Err(_) => {
            // Parse error acceptable for Bash extensions
        }
    }

    // Summary:
    // POSIX pipes: Fully supported (|, multi-stage, $? exit status)
    // Bash extensions: NOT SUPPORTED (PIPESTATUS, pipefail, |&, etc.)
    // bashrs: Generate POSIX-compliant pipelines only
}

// ============================================================================
// CMD-LIST-001: Command Lists (&&, ||, ;) (POSIX, SUPPORTED)
// ============================================================================
//
// Task: CMD-LIST-001 (3.2.3.1) - Document command lists (&&, ||, ;)
// Status: DOCUMENTED (SUPPORTED - POSIX compliant)
// Priority: HIGH (fundamental control flow)
//
// Command lists connect multiple commands with control flow operators.
// These are core POSIX features available in all shells.
//
// POSIX operators:
// - ; (semicolon): Execute sequentially, ignore exit status
// - && (AND): Execute second command only if first succeeds (exit 0)
// - || (OR): Execute second command only if first fails (exit non-zero)
// - Newline: Equivalent to semicolon
//
// bashrs policy:
// - FULLY SUPPORTED (POSIX compliant)
// - Quote all variables in generated shell
// - Preserve short-circuit evaluation semantics
// - Map to if statements in Rust

#[test]
fn test_CMD_LIST_001_semicolon_sequential() {
    // DOCUMENTATION: Semicolon (;) executes commands sequentially
    //
    // Semicolon executes commands in sequence, regardless of exit status:
    // $ cmd1 ; cmd2 ; cmd3
    // (All three commands execute, regardless of success/failure)
    //
    // $ false ; echo "Still runs"
    // Still runs
    //
    // Newline is equivalent to semicolon:
    // $ cmd1
    // $ cmd2
    // (Same as: cmd1 ; cmd2)
    //
    // POSIX-compliant: Works in sh, dash, ash, bash

    let sequential = r#"
echo "First"
echo "Second"
false
echo "Third"
"#;

    let result = BashParser::new(sequential);
    assert!(result.is_ok(), "Sequential commands should parse (POSIX)");

    let mut parser = result.unwrap();
    let parse_result = parser.parse();
    assert!(
        parse_result.is_ok() || parse_result.is_err(),
        "Semicolon/newline separation is POSIX-compliant"
    );
}

#[test]
fn test_CMD_LIST_001_and_operator_short_circuit() {
    // DOCUMENTATION: AND operator (&&) with short-circuit evaluation
    //
    // AND (&&) executes second command only if first succeeds:
    // $ test -f file.txt && echo "File exists"
    // (echo only runs if test succeeds)
    //
    // $ false && echo "Never printed"
    // (echo never runs because false returns 1)
    //
    // Short-circuit: Right side only evaluated if left succeeds
    // Exit status: Status of last executed command
    //
    // POSIX-compliant: SUSv3, IEEE Std 1003.1-2001

    let and_operator = r#"
test -f file.txt && echo "File exists"
true && echo "This prints"
false && echo "This does not print"
"#;

    let result = BashParser::new(and_operator);
    match result {
        Ok(mut parser) => {
            let parse_result = parser.parse();
            assert!(
                parse_result.is_ok() || parse_result.is_err(),
                "AND operator is POSIX-compliant"
            );
        }
        Err(_) => {
            // Parse error acceptable - && may not be fully implemented yet
        }
    }
}

#[test]
fn test_CMD_LIST_001_or_operator_short_circuit() {
    // DOCUMENTATION: OR operator (||) with short-circuit evaluation
    //
    // OR (||) executes second command only if first fails:
    // $ test -f file.txt || echo "File not found"
    // (echo only runs if test fails)
    //
    // $ true || echo "Never printed"
    // (echo never runs because true returns 0)
    //
    // Short-circuit: Right side only evaluated if left fails
    // Exit status: Status of last executed command
    //
    // POSIX-compliant: SUSv3, IEEE Std 1003.1-2001

    let or_operator = r#"
test -f missing.txt || echo "File not found"
false || echo "This prints"
true || echo "This does not print"
"#;

    let result = BashParser::new(or_operator);
    match result {
        Ok(mut parser) => {
            let parse_result = parser.parse();
            assert!(
                parse_result.is_ok() || parse_result.is_err(),
                "OR operator is POSIX-compliant"
            );
        }
        Err(_) => {
            // Parse error acceptable - || may not be fully implemented yet
        }
    }
}

#[test]
fn test_CMD_LIST_001_combined_operators() {
    // DOCUMENTATION: Combining &&, ||, and ; operators
    //
    // Operators can be combined with precedence rules:
    // - && and || have equal precedence, evaluated left-to-right
    // - ; has lower precedence (separates complete lists)
    //
    // Example: cmd1 && cmd2 || cmd3 ; cmd4
    // Meaning: (cmd1 AND cmd2) OR cmd3, THEN cmd4
    // 1. If cmd1 succeeds, run cmd2
    // 2. If either cmd1 or cmd2 fails, run cmd3
    // 3. Always run cmd4 (semicolon ignores previous exit status)
    //
    // Common pattern (error handling):
    // command && echo "Success" || echo "Failed"

    let combined = r#"
#!/bin/sh
# Try command, report success or failure
test -f file.txt && echo "Found" || echo "Not found"

# Multiple steps with fallback
mkdir -p /tmp/test && cd /tmp/test || exit 1

# Always cleanup, regardless of previous status
process_data && echo "Done" || echo "Error" ; cleanup
"#;

    let result = BashParser::new(combined);
    match result {
        Ok(mut parser) => {
            let parse_result = parser.parse();
            assert!(
                parse_result.is_ok() || parse_result.is_err(),
                "Combined operators are POSIX-compliant"
            );
        }
        Err(_) => {
            // Parse error acceptable - complex lists may not be fully implemented
        }
    }
}

#[test]
fn test_CMD_LIST_001_exit_status_semantics() {
    // DOCUMENTATION: Exit status with command lists
    //
    // Exit status rules:
    // - Semicolon (;): Status of last command in list
    // - AND (&&): Status of last executed command
    // - OR (||): Status of last executed command
    //
    // Examples:
    // $ true ; false
    // $ echo $?
    // 1  (status of 'false')
    //
    // $ true && echo "yes"
    // yes
    // $ echo $?
    // 0  (status of 'echo')
    //
    // $ false || echo "fallback"
    // fallback
    // $ echo $?
    // 0  (status of 'echo')

    let exit_status = r#"
#!/bin/sh
# Exit status examples
true ; false
if [ $? -ne 0 ]; then
    echo "Last command failed"
fi

true && echo "Success"
if [ $? -eq 0 ]; then
    echo "Previous succeeded"
fi

false || echo "Fallback"
if [ $? -eq 0 ]; then
    echo "Fallback succeeded"
fi
"#;

    let result = BashParser::new(exit_status);
    match result {
        Ok(mut parser) => {
            let parse_result = parser.parse();
            assert!(
                parse_result.is_ok() || parse_result.is_err(),
                "Exit status semantics are POSIX-compliant"
            );
        }
        Err(_) => {
            // Parse error acceptable
        }
    }
}

// DOCUMENTATION: Rust if statement mapping for command lists
//
// Bash AND (&&):
// test -f file.txt && echo "File exists"
//
// Rust equivalent:
//   fn handle() { if test_file("file.txt") { println!("File exists"); } }
//
// Bash OR (||):
// test -f file.txt || echo "File not found"
//
// Rust equivalent:
//   fn handle() { if !test_file("file.txt") { println!("File not found"); } }
//
// Bash combined (&&/||):
// cmd1 && cmd2 || cmd3
//
// Rust equivalent:
//   fn handle() { if cmd1() { cmd2(); } else { cmd3(); } }
//
// bashrs strategy:
// - Map && to statement
// - Map || to negated condition
// - Preserve short-circuit evaluation semantics
#[test]
fn test_CMD_LIST_001_rust_if_statement_mapping() {
    // This test documents the Rust mapping strategy
}

#[test]
fn test_CMD_LIST_001_common_patterns() {
    // DOCUMENTATION: Common command list patterns
    //
    // Pattern 1: Error checking
    // command || exit 1
    // (Exit if command fails)
    //
    // Pattern 2: Success confirmation
    // command && echo "Done"
    // (Print message only if succeeds)
    //
    // Pattern 3: Try-catch style
    // command && echo "Success" || echo "Failed"
    // (Report outcome either way)
    //
    // Pattern 4: Safe directory change
    // cd /path || exit 1
    // (Exit if cd fails)
    //
    // Pattern 5: Create and enter
    // mkdir -p dir && cd dir
    // (Only cd if mkdir succeeds)
    //
    // Pattern 6: Cleanup always runs
    // process ; cleanup
    // (Cleanup runs regardless of process exit status)

    let common_patterns = r#"
#!/bin/sh
# Pattern 1: Error checking
command || exit 1

# Pattern 2: Success confirmation
command && echo "Done"

# Pattern 3: Try-catch style
command && echo "Success" || echo "Failed"

# Pattern 4: Safe directory change
cd /path || exit 1

# Pattern 5: Create and enter
mkdir -p dir && cd dir

# Pattern 6: Cleanup always runs
process_data ; cleanup_resources
"#;

    let result = BashParser::new(common_patterns);
    match result {
        Ok(mut parser) => {
            let parse_result = parser.parse();
            assert!(
                parse_result.is_ok() || parse_result.is_err(),
                "Common patterns are POSIX-compliant"
            );
        }
        Err(_) => {
            // Parse error acceptable
        }
    }
}

#[test]

include!("part3_cmd_list.rs");