ripex 0.2.0

Multi-language structural parsing and fact extraction.
Documentation
mod fixtures;

use crate::rust::facts;
use crate::rust::lexer::{Lexer, TokenKind};
use crate::rust::parse_program;

#[test]
fn test_lexer_basic_tokens() {
    let (tokens, errors) = Lexer::new("fn main() { return 42; }").tokenize();
    assert!(errors.is_empty(), "lexer errors: {:?}", errors);
    let kinds: Vec<TokenKind> = tokens.iter().map(|t| t.kind).collect();
    assert_eq!(
        kinds,
        vec![
            TokenKind::Fn,
            TokenKind::Ident,
            TokenKind::LParen,
            TokenKind::RParen,
            TokenKind::LBrace,
            TokenKind::Return,
            TokenKind::IntLit,
            TokenKind::Semicolon,
            TokenKind::RBrace,
            TokenKind::Eof,
        ]
    );
}

#[test]
fn test_parse_empty() {
    let (program, errors) = parse_program("");
    assert!(
        errors.is_empty(),
        "expected no parse errors, got: {:?}",
        errors
    );
    assert!(program.items.is_empty());
}

#[test]
fn test_parse_imports() {
    let (program, errors) = parse_program(fixtures::IMPORT_SIMPLE);
    assert!(errors.is_empty(), "parse errors: {:?}", errors);
    let result = facts::extract_facts(&program);
    assert!(!result.imports.is_empty(), "expected imports");
}

#[test]
fn test_parse_functions() {
    let func_sources = &[
        fixtures::FN_EMPTY,
        fixtures::FN_PARAMS,
        fixtures::FN_GENERIC,
    ];
    for &src in func_sources {
        let (program, errors) = parse_program(src);
        assert!(errors.is_empty(), "parse errors in {:?}: {:?}", src, errors);
        let result = facts::extract_facts(&program);
        assert!(!result.symbols.is_empty(), "expected symbols in: {src}");
    }
}

#[test]
fn test_macro_calls_extracted() {
    let (program, errors) = parse_program("fn main() { let v = vec![1, 2, 3]; println!(\"hi\"); }");
    assert!(errors.is_empty(), "parse errors: {:?}", errors);
    let calls = facts::extract_calls(&program);
    let names: Vec<&str> = calls.iter().map(|c| c.callee_text.as_str()).collect();
    assert!(
        names.contains(&"vec"),
        "macro vec! not extracted: {:?}",
        names
    );
    assert!(
        names.contains(&"println"),
        "macro println! not extracted: {:?}",
        names
    );
}

#[test]
fn test_parse_structs_enums() {
    let type_sources = &[
        fixtures::STRUCT_SIMPLE,
        fixtures::ENUM_SIMPLE,
        fixtures::TRAIT_DECL,
        fixtures::IMPL_BLOCK,
        fixtures::CONSTRUCTOR,
        fixtures::ASSOCIATED_FN,
    ];
    for &src in type_sources {
        let (program, errors) = parse_program(src);
        assert!(errors.is_empty(), "parse errors in {:?}: {:?}", src, errors);
        let result = facts::extract_facts(&program);
        assert!(!result.symbols.is_empty(), "expected symbols in: {src}");
    }
}

#[test]
fn test_parse_variables() {
    let var_sources = &[fixtures::LET_VAR, fixtures::LET_MUT];
    for &src in var_sources {
        let (program, errors) = parse_program(src);
        assert!(errors.is_empty(), "parse errors in {:?}: {:?}", src, errors);
        let result = facts::extract_facts(&program);
        assert!(!result.variables.is_empty(), "expected variables in: {src}");
    }
}

#[test]
fn test_struct_simple() {
    let (p, e) = parse_program(fixtures::STRUCT_SIMPLE);
    assert!(e.is_empty(), "{:?}", e);
    let r = facts::extract_facts(&p);
    assert!(!r.symbols.is_empty());
}
#[test]
fn test_enum_simple() {
    let (p, e) = parse_program(fixtures::ENUM_SIMPLE);
    assert!(e.is_empty(), "{:?}", e);
    let r = facts::extract_facts(&p);
    assert!(!r.symbols.is_empty());
}
#[test]
fn test_trait_decl() {
    let (p, e) = parse_program(fixtures::TRAIT_DECL);
    assert!(e.is_empty(), "{:?}", e);
    let r = facts::extract_facts(&p);
    assert!(!r.symbols.is_empty());
}
#[test]
fn test_impl_block() {
    let (p, e) = parse_program(fixtures::IMPL_BLOCK);
    assert!(e.is_empty(), "{:?}", e);
    let r = facts::extract_facts(&p);
    assert!(!r.symbols.is_empty());
}
#[test]
fn test_constructor() {
    let (p, e) = parse_program(fixtures::CONSTRUCTOR);
    assert!(e.is_empty(), "{:?}", e);
    let r = facts::extract_facts(&p);
    assert!(!r.symbols.is_empty());
}
#[test]
fn test_associated_fn() {
    let (p, e) = parse_program(fixtures::ASSOCIATED_FN);
    assert!(e.is_empty(), "{:?}", e);
    let r = facts::extract_facts(&p);
    assert!(!r.symbols.is_empty());
}
#[test]
fn test_import_nested_is_atomic_and_has_no_blank_calls() {
    let (program, errors) = parse_program(fixtures::IMPORT_NESTED);
    assert!(errors.is_empty(), "parse errors: {:?}", errors);
    let result = facts::extract_facts(&program);
    let names: Vec<&str> = result
        .imports
        .iter()
        .map(|import| import.source.as_str())
        .collect();
    assert!(
        names.contains(&"std::io"),
        "missing self import: {:?}",
        names
    );
    assert!(
        names.contains(&"std::io::Read"),
        "missing nested import: {:?}",
        names
    );
    assert!(
        result.calls.iter().all(|call| !call.callee_text.is_empty()),
        "blank call fact emitted: {:?}",
        result.calls
    );
}
#[test]
fn test_fn_async() {
    parse_program(fixtures::FN_ASYNC);
}
#[test]
fn test_if_else() {
    parse_program(fixtures::IF_ELSE);
}
#[test]
fn test_for_loop() {
    parse_program(fixtures::FOR_LOOP);
}
#[test]
fn test_while_loop() {
    parse_program(fixtures::WHILE_LOOP);
}
#[test]
fn test_loop_inf() {
    parse_program(fixtures::LOOP_INF);
}
#[test]
fn test_match_expr() {
    parse_program(fixtures::MATCH_EXPR);
}
#[test]
fn test_macro_vec() {
    parse_program(fixtures::MACRO_VEC);
}
#[test]
fn test_macro_println() {
    parse_program(fixtures::MACRO_PRINTLN);
}
#[test]
fn test_attr_derive() {
    parse_program(fixtures::ATTR_DERIVE);
}
#[test]
fn test_attr_test() {
    parse_program(fixtures::ATTR_TEST);
}

#[test]
fn malformed_function_head_never_panics() {
    for (language, parser) in crate::registry() {
        let parsed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            let result = parser.parse("fn ma {in(\n");
            let _ = parser.extract(&result);
            let _ = parser.extract_best_effort(&result);
        }));
        assert!(
            parsed.is_ok(),
            "{language} parser panicked on a truncated function head"
        );
    }
}

#[test]
fn fuzzed_empty_callee_never_panics() {
    for (language, parser) in crate::registry() {
        let parsed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            let result = parser.parse("nn\0` \r{in8\n");
            let _ = parser.extract(&result);
            let _ = parser.extract_best_effort(&result);
        }));
        assert!(
            parsed.is_ok(),
            "{language} parser panicked on a fuzzed empty-callee input"
        );
    }
}