fn test_parse_assignment_append() {
let stmt = parse_first("x+=more");
match stmt {
BashStmt::Assignment { name, value, .. } => {
assert_eq!(name, "x");
match &value {
BashExpr::Literal(s) => assert_eq!(s, "more"),
other => panic!("expected Literal value for append, got {other:?}"),
}
}
other => panic!("expected Assignment for 'x+=more', got {other:?}"),
}
}
#[test]
fn test_parse_assignment_append_string() {
let stmt = parse_first(r#"path+="/bin""#);
match stmt {
BashStmt::Assignment { name, value, .. } => {
assert_eq!(name, "path");
assert_eq!(value, BashExpr::Literal("/bin".to_string()));
}
other => panic!("expected Assignment for append with string, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_empty() {
let stmt = parse_first("x=");
match stmt {
BashStmt::Assignment {
name, value, index, ..
} => {
assert_eq!(name, "x");
assert!(index.is_none());
assert_eq!(value, BashExpr::Literal(String::new()));
}
other => panic!("expected Assignment with empty value, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_empty_followed_by_newline() {
let mut parser = BashParser::new("x=\necho hello").unwrap();
let ast = parser.parse().unwrap();
assert!(ast.statements.len() >= 2, "expected at least 2 statements");
match &ast.statements[0] {
BashStmt::Assignment { name, value, .. } => {
assert_eq!(name, "x");
assert_eq!(*value, BashExpr::Literal(String::new()));
}
other => panic!("expected empty Assignment, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_exported() {
let stmt = parse_first("export x=5");
match stmt {
BashStmt::Assignment {
name,
value,
exported,
..
} => {
assert_eq!(name, "x");
assert_eq!(value, BashExpr::Literal("5".to_string()));
assert!(exported, "expected exported=true");
}
other => panic!("expected exported Assignment, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_exported_string() {
let stmt = parse_first(r#"export PATH="/usr/bin""#);
match stmt {
BashStmt::Assignment {
name,
value,
exported,
..
} => {
assert_eq!(name, "PATH");
assert_eq!(value, BashExpr::Literal("/usr/bin".to_string()));
assert!(exported);
}
other => panic!("expected exported Assignment, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_exported_empty() {
let stmt = parse_first("export VAR=");
match stmt {
BashStmt::Assignment {
name,
value,
exported,
..
} => {
assert_eq!(name, "VAR");
assert_eq!(value, BashExpr::Literal(String::new()));
assert!(exported);
}
other => panic!("expected exported empty Assignment, got {other:?}"),
}
}
#[test]
fn test_parse_multiple_assignments() {
let mut parser = BashParser::new("a=1\nb=2\nc=3").unwrap();
let ast = parser.parse().unwrap();
assert_eq!(ast.statements.len(), 3);
for (i, expected_name) in ["a", "b", "c"].iter().enumerate() {
match &ast.statements[i] {
BashStmt::Assignment { name, .. } => {
assert_eq!(name, *expected_name);
}
other => panic!("expected Assignment at index {i}, got {other:?}"),
}
}
}
#[test]
fn test_parse_assignment_variable_value() {
let stmt = parse_first("x=$HOME");
match stmt {
BashStmt::Assignment { name, value, .. } => {
assert_eq!(name, "x");
match &value {
BashExpr::Variable(v) => assert_eq!(v, "HOME"),
BashExpr::Concat(parts) => {
assert!(!parts.is_empty());
}
other => panic!("expected Variable or Concat value, got {other:?}"),
}
}
other => panic!("expected Assignment, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_not_exported() {
let stmt = parse_first("y=42");
match stmt {
BashStmt::Assignment { exported, .. } => {
assert!(!exported, "regular assignment should have exported=false");
}
other => panic!("expected Assignment, got {other:?}"),
}
}
#[test]
fn test_parse_assignment_unterminated_string() {
parse_err(r#"x="unclosed"#);
}
#[test]
fn test_parse_assignment_unterminated_string_single_quote() {
parse_err("x='unclosed");
}
#[test]
fn test_parse_assignment_missing_bracket_not_array() {
let stmt = parse_first("arr[0=val");
match &stmt {
BashStmt::Assignment { index, .. } => {
assert!(index.is_none(), "should not have an array index");
}
BashStmt::Command { .. } => {
}
_ => {
}
}
}