#![allow(missing_docs)]
use predicates::prelude::*;
use proptest::prelude::*;
use std::fs;
fn lint_code(code: &str, test_name: &str) -> assert_cmd::assert::Assert {
let temp_file = format!("/tmp/quality_015_{test_name}.ruchy");
fs::write(&temp_file, code).unwrap();
let assert = assert_cmd::cargo::cargo_bin_cmd!("ruchy")
.arg("lint")
.arg(&temp_file)
.assert();
let _ = fs::remove_file(&temp_file);
assert
}
#[test]
fn test_quality_015_01_used_function_not_flagged() {
let code = r#"
fun add(a: i32, b: i32) -> i32 {
a + b
}
fun main() {
let result = add(5, 3)
println("Result: {}", result)
}
"#;
lint_code(code, "01")
.success()
.stdout(predicate::str::contains("unused variable: add").not())
.stdout(predicate::str::contains("unused variable: main").not());
}
#[test]
fn test_quality_015_02_multiple_used_functions_not_flagged() {
let code = r#"
fun add(a: i32, b: i32) -> i32 {
a + b
}
fun greet(name: str) {
println("Hello, {}!", name)
}
fun main() {
let result = add(5, 3)
println("Result: {}", result)
greet("World")
}
"#;
lint_code(code, "02")
.success()
.stdout(predicate::str::contains("unused variable: add").not())
.stdout(predicate::str::contains("unused variable: greet").not())
.stdout(predicate::str::contains("unused variable: main").not());
}
#[test]
fn test_quality_015_03_main_not_flagged_as_variable() {
let code = r#"
fun main() {
let x = 5
println("x = {}", x)
}
"#;
lint_code(code, "03")
.success()
.stdout(predicate::str::contains("unused variable: main").not());
}
#[test]
fn test_quality_015_04_mutual_function_calls() {
let code = r#"
fun helper(x: i32) -> i32 {
x * 2
}
fun process(data: i32) -> i32 {
helper(data) + 10
}
fun main() {
let result = process(5)
println("Result: {}", result)
}
"#;
lint_code(code, "04")
.success()
.stdout(predicate::str::contains("unused variable: helper").not())
.stdout(predicate::str::contains("unused variable: process").not())
.stdout(predicate::str::contains("unused variable: main").not());
}
#[test]
fn test_quality_015_05_unused_local_variable_still_flagged() {
let code = r#"
fun main() {
let x = 5
let y = 10
println("Sum: {}", x + y)
let unused = 42
}
"#;
lint_code(code, "05")
.success()
.stdout(predicate::str::contains("unused variable: unused"));
}
#[test]
fn test_quality_015_06_unused_variable_in_function_body() {
let code = r#"
fun calculate(a: i32, b: i32) -> i32 {
let temp = a + b
let unused = 99
temp
}
fun main() {
let result = calculate(5, 3)
println("Result: {}", result)
}
"#;
lint_code(code, "06")
.success()
.stdout(predicate::str::contains("unused variable: unused"))
.stdout(predicate::str::contains("unused variable: calculate").not())
.stdout(predicate::str::contains("unused variable: main").not());
}
#[test]
fn test_quality_015_07_truly_unused_function() {
let code = r#"
fun used_function() -> i32 {
42
}
fun unused_function() -> i32 {
99
}
fun main() {
let result = used_function()
println("Result: {}", result)
}
"#;
lint_code(code, "07")
.success()
.stdout(predicate::str::contains("unused variable: used_function").not())
.stdout(predicate::str::contains("unused variable: unused_function").not());
}
#[test]
fn test_quality_015_08_github_issue_15_simple_case() {
let code = r#"
fun main() {
let x = 5
let y = 10
println("Sum: {}", x + y)
let unused = 42
}
"#;
lint_code(code, "08")
.success()
.stdout(predicate::str::contains("unused variable: unused"))
.stdout(predicate::str::contains("unused variable: x").not())
.stdout(predicate::str::contains("unused variable: y").not())
.stdout(predicate::str::contains("unused variable: main").not());
}
#[test]
fn test_quality_015_09_github_issue_15_function_case() {
let code = r#"
fun add(a: i32, b: i32) -> i32 {
a + b
}
fun greet(name: str) {
println("Hello, {}!", name)
}
fun main() {
let result = add(5, 3)
println("Result: {}", result)
greet("World")
}
"#;
lint_code(code, "09")
.success()
.stdout(predicate::str::contains("unused variable: add").not())
.stdout(predicate::str::contains("unused variable: greet").not())
.stdout(predicate::str::contains("unused variable: main").not())
.stdout(predicate::str::contains("unused variable: result").not());
}
fn function_name_strategy() -> impl Strategy<Value = String> {
prop::string::string_regex("[a-z][a-z0-9_]{0,15}").expect("valid regex")
}
fn function_body_strategy() -> impl Strategy<Value = String> {
prop_oneof![
Just("42".to_string()),
Just("a + b".to_string()),
Just("x * 2".to_string()),
Just("\"hello\"".to_string()),
Just("true".to_string()),
Just("false".to_string()),
]
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
#[test]
fn test_quality_015_10_property_used_functions_never_flagged(
fn_name in function_name_strategy(),
body in function_body_strategy()
) {
if fn_name == "main" || fn_name == "if" || fn_name == "let" || fn_name == "fun" {
return Ok(());
}
let code = format!(
r#"
fun {fn_name}() -> i32 {{
{body}
}}
fun main() {{
let result = {fn_name}()
println("Result: {{}}", result)
}}
"#
);
let temp_file = format!("/tmp/quality_015_prop_{fn_name}.ruchy");
fs::write(&temp_file, &code).unwrap();
let output = assert_cmd::cargo::cargo_bin_cmd!("ruchy")
.arg("lint")
.arg(&temp_file)
.output()
.unwrap();
let _ = fs::remove_file(&temp_file);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let unused_msg = format!("unused variable: {fn_name}");
prop_assert!(
!stdout.contains(&unused_msg) && !stderr.contains(&unused_msg),
"Function '{}' was incorrectly flagged as unused variable\nStdout: {}\nStderr: {}",
fn_name, stdout, stderr
);
}
#[test]
#[ignore = "Long-running property test - run with --ignored"]
fn test_quality_015_11_property_unused_locals_always_flagged(
var_name in prop::string::string_regex("[a-z][a-z0-9_]{0,15}").expect("valid regex")
) {
if var_name == "main" || var_name == "if" || var_name == "let" || var_name == "fun" {
return Ok(());
}
let code = format!(
r#"
fun main() {{
let x = 5
let {var_name} = 42
println("x = {{}}", x)
}}
"#
);
let temp_file = format!("/tmp/quality_015_prop_unused_{var_name}.ruchy");
fs::write(&temp_file, &code).unwrap();
let output = assert_cmd::cargo::cargo_bin_cmd!("ruchy")
.arg("lint")
.arg(&temp_file)
.output()
.unwrap();
let _ = fs::remove_file(&temp_file);
let stdout = String::from_utf8_lossy(&output.stdout);
let unused_msg = format!("unused variable: {var_name}");
prop_assert!(
stdout.contains(&unused_msg),
"Unused variable '{}' was NOT flagged (regression)\nStdout: {}",
var_name, stdout
);
}
#[test]
fn test_quality_015_12_property_main_never_flagged(
body in function_body_strategy()
) {
let code = format!(
r#"
fun main() {{
let x = {body}
println("x = {{}}", x)
}}
"#
);
let temp_file = "/tmp/quality_015_prop_main.ruchy";
fs::write(temp_file, &code).unwrap();
let output = assert_cmd::cargo::cargo_bin_cmd!("ruchy")
.arg("lint")
.arg(temp_file)
.output()
.unwrap();
let _ = fs::remove_file(temp_file);
let stdout = String::from_utf8_lossy(&output.stdout);
prop_assert!(
!stdout.contains("unused variable: main"),
"main() was incorrectly flagged as unused\nStdout: {}",
stdout
);
}
}