rusty-javac 0.2.2

A Java compiler written in Rust.
Documentation
use rowan::SyntaxNode;
use rusty_javac::ast::{JavaLanguage, JavaSyntaxKind};
use rusty_javac::parser::{Parse, Parser};
use std::path::{Path, PathBuf};
use std::process::Command;

fn workspace_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf()
}

fn fixtures_dir() -> PathBuf {
    workspace_root().join("tests").join("java")
}

fn java_fixtures() -> Vec<PathBuf> {
    let mut fixtures = std::fs::read_dir(fixtures_dir())
        .expect("failed to read root tests/java directory")
        .map(|entry| entry.expect("failed to read java fixture entry").path())
        .filter(|path| path.extension().is_some_and(|ext| ext == "java"))
        .collect::<Vec<_>>();
    fixtures.sort();
    assert!(!fixtures.is_empty(), "expected at least one Java fixture");
    fixtures
}

fn assert_no_errors(parse: &Parse, file: &Path) {
    if !parse.errors.is_empty() {
        let msgs: Vec<String> = parse
            .errors
            .iter()
            .map(|e| format!("  offset {}: {}", e.offset, e.message))
            .collect();
        panic!("parse errors in {}:\n{}", file.display(), msgs.join("\n"));
    }
}

#[test]
fn parse_all_java_fixtures() {
    for path in java_fixtures() {
        let source = std::fs::read_to_string(&path)
            .unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
        let parse = Parser::parse(&source);
        assert_no_errors(&parse, &path);
    }
}

#[test]
fn parser_builds_green_tree_for_all_java_fixtures() {
    for path in java_fixtures() {
        let source = std::fs::read_to_string(&path)
            .unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
        let parse = Parser::parse(&source);
        assert_no_errors(&parse, &path);
        let root = SyntaxNode::<JavaLanguage>::new_root(parse.green_node);
        assert_eq!(root.kind(), JavaSyntaxKind::CompilationUnit);
        assert!(
            !root.text().to_string().is_empty(),
            "green tree should contain parsed tokens for {}",
            path.display()
        );
    }
}

#[test]
fn javac_accepts_all_java_fixtures() {
    let output_root = workspace_root().join("target").join("java-fixture-classes");
    std::fs::create_dir_all(&output_root)
        .unwrap_or_else(|e| panic!("failed to create {}: {}", output_root.display(), e));

    for path in java_fixtures() {
        let fixture_output = output_root.join(
            path.file_stem()
                .expect("java fixture should have a file stem"),
        );
        let _ = std::fs::remove_dir_all(&fixture_output);
        std::fs::create_dir_all(&fixture_output)
            .unwrap_or_else(|e| panic!("failed to create {}: {}", fixture_output.display(), e));

        let output = Command::new("javac")
            .arg("--release")
            .arg("21")
            .arg("-d")
            .arg(&fixture_output)
            .arg(&path)
            .output()
            .unwrap_or_else(|e| panic!("failed to run javac for {}: {}", path.display(), e));

        assert!(
            output.status.success(),
            "javac failed for {}\nstdout:\n{}\nstderr:\n{}",
            path.display(),
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr),
        );
    }
}