alef 0.84.0

Opinionated polyglot binding generator for Rust libraries
Documentation
//! Every scaffold file alef *claims* must also be *stamped*, and the stamp must land where
//! `poly` looks for it.
//!
//! The two tools disagree about what "generated" means. alef claims a file when
//! `core::hash::content_has_alef_marker` finds its prose header ("auto-generated by alef" /
//! "Generated by alef") anywhere in the leading lines. poly's built-in generated-file skip
//! ignores prose entirely: it skips a file only when one of the first
//! `POLY_GENERATED_SCAN_LINES` lines carries a `<tool>:hash:<40-or-64 hex>` line. A file with
//! alef's header and no `alef:hash:` line therefore sits in the gap — alef owns it, poly
//! reformats it, and alef rewrites poly's formatting on the next run. Closing the gap on alef's
//! side means: anything claimed is stamped, and the stamp is inside poly's window.

use super::*;
use crate::cli::pipeline::generate::ensure_generated_header;
use crate::core::hash::{
    POLY_GENERATED_SCAN_LINES, content_has_alef_marker, deepest_hash_line, extract_hash, inject_hash_line,
};

/// A syntactically valid stamp value. `finalize_hashes` derives the real one from the generation
/// inputs and the formatted body; neither affects *where* the line lands, which is what these
/// tests measure.
const STAMP: &str = "0ce4d753fdb4854e44358639dcbaebee3449a4afa142dbc4f0a72aa72c214648";

/// Reproduce the bytes a `GeneratedFile` reaches disk with, for marker purposes: the write
/// pipeline's `ensure_generated_header` pass followed by `finalize_hashes`'s `inject_hash_line`.
///
/// Deliberately skips `normalize_content`: it force-appends a trailing newline, which would make
/// every trailing-newline assertion below pass no matter what the emitter produced. The emitter's
/// own bytes are the subject. ~keep
fn as_written(file: &GeneratedFile) -> String {
    let headered = if file.generated_header {
        ensure_generated_header(&file.path, &file.content)
    } else {
        file.content.clone()
    };
    inject_hash_line(&headered, STAMP)
}

/// 1-based line number of the `alef:hash:` line, or `None` when nothing was stamped.
fn stamp_line_number(content: &str) -> Option<usize> {
    content
        .lines()
        .position(|line| line.contains("alef:hash:"))
        .map(|index| index + 1)
}

/// The emitter's own bytes must end with a newline.
///
/// Kept separate from [`assert_claim_is_stamped_inside_polys_window`] and applied only to the
/// named writers, not to the `Language::ALL` sweep: `normalize_content` force-appends a trailing
/// newline on both write rails, so a violation is invisible on disk and only reaches direct
/// readers of `GeneratedFile::content`. Sweeping every emitter for it would fold an unmeasured
/// population into a test whose real subject is the marker gap. ~keep
fn assert_content_ends_with_newline(file: &GeneratedFile) {
    let tail: String = {
        let mut characters: Vec<char> = file.content.chars().rev().take(24).collect();
        characters.reverse();
        characters.into_iter().collect()
    };
    assert!(
        file.content.ends_with('\n'),
        "{}: emitter produced content with no trailing newline. `normalize_content` hides this on \
         the write path, but every direct reader of `GeneratedFile::content` (diff, verify-side \
         comparisons, tests) sees the raw bytes; got tail {tail:?}",
        file.path.display()
    );
}

/// The full contract for one emitted file: if alef claims it, the stamp exists, round-trips, and
/// sits inside poly's scan window.
fn assert_claim_is_stamped_inside_polys_window(file: &GeneratedFile) {
    let path = file.path.display().to_string();
    let written = as_written(file);

    assert!(
        content_has_alef_marker(&written),
        "{path}: emitted with `generated_header: true` but no alef marker survives \
         `ensure_generated_header`, so `finalize_hashes` will never stamp it; got:\n{}",
        written.lines().take(5).collect::<Vec<_>>().join("\n")
    );
    assert_eq!(
        extract_hash(&written).as_deref(),
        Some(STAMP),
        "{path}: marker is present but `extract_hash` cannot recover the injected stamp, so \
         `alef verify` reads the file as unstamped; got:\n{}",
        written.lines().take(5).collect::<Vec<_>>().join("\n")
    );

    let stamp_line = stamp_line_number(&written)
        .unwrap_or_else(|| panic!("{path}: claimed by alef but `inject_hash_line` wrote no stamp at all"));
    assert!(
        stamp_line <= POLY_GENERATED_SCAN_LINES,
        "{path}: stamp landed on line {stamp_line}, past the first {POLY_GENERATED_SCAN_LINES} \
         lines poly reads. poly will not skip this file, so it reformats content alef owns and \
         alef rewrites the formatting on the next run"
    );
}

/// Locate one emitted file by its full relative path, failing loudly rather than silently
/// checking nothing when the emitter stops producing it.
fn require<'a>(files: &'a [GeneratedFile], relative_path: &str) -> &'a GeneratedFile {
    files
        .iter()
        .find(|file| file.path.to_string_lossy() == relative_path)
        .unwrap_or_else(|| {
            let emitted: Vec<String> = files.iter().map(|f| f.path.to_string_lossy().into_owned()).collect();
            panic!("{relative_path} was not emitted at all, so the marker assertions examine nothing; got {emitted:?}")
        })
}

#[test]
fn rustfmt_toml_is_stamped_inside_polys_scan_window() {
    let files = scaffold(&test_api(), &test_config(), &[Language::Python, Language::Node]).unwrap();
    let rustfmt = require(&files, "rustfmt.toml");
    assert_content_ends_with_newline(rustfmt);
    assert_claim_is_stamped_inside_polys_window(rustfmt);
}

#[test]
fn poly_toml_is_stamped_inside_polys_scan_window() {
    let files = scaffold(&test_api(), &test_config(), &[Language::Python, Language::Node]).unwrap();
    let poly = require(&files, "poly.toml");
    assert_content_ends_with_newline(poly);
    assert_claim_is_stamped_inside_polys_window(poly);
}

#[test]
fn python_pyproject_toml_is_stamped_inside_polys_scan_window() {
    let files = scaffold(&test_api(), &test_config(), &[Language::Python]).unwrap();
    let pyproject = files
        .iter()
        .find(|file| file.path.file_name().is_some_and(|name| name == "pyproject.toml"))
        .unwrap_or_else(|| panic!("python scaffold emitted no pyproject.toml, so this test examines nothing"));
    assert_content_ends_with_newline(pyproject);
    assert_claim_is_stamped_inside_polys_window(pyproject);
}

/// Called on the emitter rather than through `scaffold`, because `scaffold` suppresses this file
/// whenever `rust-toolchain.toml` exists **in the process CWD** — and alef's own repo root has
/// one, so a `scaffold`-driven assertion here would be vacuous in exactly the tree that runs it.
#[test]
fn rust_toolchain_toml_is_stamped_inside_polys_scan_window() {
    for languages in [Vec::new(), vec![Language::Wasm]] {
        let file = rust_toolchain_file(&languages);
        assert_eq!(
            file.path,
            std::path::PathBuf::from("rust-toolchain.toml"),
            "the seed must still be the repo-root toolchain file"
        );
        assert!(
            file.content.contains("[toolchain]"),
            "control: the seed must still emit a `[toolchain]` table, else the marker assertions \
             cover an empty file; got:\n{}",
            file.content
        );
        assert_content_ends_with_newline(&file);
        assert_claim_is_stamped_inside_polys_window(&file);
    }
}

/// Same CWD reasoning as [`rust_toolchain_toml_is_stamped_inside_polys_scan_window`]: the wasm
/// `.cargo/config.toml` branch is gated on the file's absence from the process CWD.
#[test]
fn wasm_cargo_config_is_stamped_inside_polys_scan_window() {
    let file = wasm_cargo_config_file();
    assert!(
        file.content.contains("[target.wasm32-unknown-unknown]"),
        "control: the wasm seed must still carry its wasm32 target table; got:\n{}",
        file.content
    );
    assert_content_ends_with_newline(&file);
    assert_claim_is_stamped_inside_polys_window(&file);
}

/// The `[scaffold.cargo]` branch writes the same path through `render_cargo_config`, which
/// hand-rolls its own header instead of taking one from `ensure_generated_header`. Both branches
/// must produce a stamped file, or which branch a repo takes decides whether poly reformats its
/// `.cargo/config.toml`.
#[test]
fn configured_cargo_config_is_stamped_inside_polys_scan_window() {
    let configured = GeneratedFile {
        path: std::path::PathBuf::from(".cargo/config.toml"),
        content: render_cargo_config(&crate::core::config::ScaffoldCargo::default()),
        generated_header: true,
    };
    assert!(
        configured.content.contains("[build]"),
        "control: the configured branch must still render a `[build]` table; got:\n{}",
        configured.content
    );
    assert_content_ends_with_newline(&configured);
    assert_claim_is_stamped_inside_polys_window(&configured);
}

/// The number of marker-rail scaffold files the sweep below must find before its per-file
/// assertions mean anything. Set well under the observed count so ordinary churn does not trip
/// it, and well above zero so a sweep that stops finding files fails instead of passing. ~keep
const MINIMUM_MARKER_RAIL_FILES: usize = 10;

/// Sweep: every scaffold file on the marker rail, for every language, must land its stamp inside
/// poly's scan window.
///
/// Driven off `Language::ALL` so a language added later is covered without editing this test, and
/// guarded by a lower bound so a sweep that examines nothing — a `scaffold` signature change, a
/// fixture that stops resolving, an emitter list that empties — fails rather than reports
/// success. That failure mode is the reason the bound is here at all: a green run over zero files
/// is indistinguishable from a green run over all of them. ~keep
#[test]
fn every_marker_rail_scaffold_file_is_stamped_inside_polys_scan_window() {
    let api = test_api();
    let config = test_config();
    let mut checked: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
    let mut unmarkable: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();

    for language in Language::ALL {
        let Ok(files) = scaffold(&api, &config, &[language]) else {
            continue;
        };
        for file in &files {
            if !file.generated_header && !content_has_alef_marker(&file.content) {
                continue;
            }
            let relative_path = file.path.to_string_lossy().into_owned();
            if !content_has_alef_marker(&as_written(file)) {
                // `generated_header: true` on a format with no comment syntax at all (`.json`,
                // `.jar`). Nothing is claimed, so nothing is in the gap — these prove ownership
                // through `cache::OWNERSHIP_MANIFEST` instead. Collected rather than ignored so
                // the split between the two rails stays visible. ~keep
                unmarkable.insert(relative_path);
                continue;
            }
            assert_claim_is_stamped_inside_polys_window(file);
            checked.insert(relative_path);
        }
    }

    assert!(
        checked.len() >= MINIMUM_MARKER_RAIL_FILES,
        "the sweep checked only {} marker-rail scaffold file(s), below the {MINIMUM_MARKER_RAIL_FILES} \
         floor -- it has gone vacuous and would pass no matter what the emitters do. Checked: \
         {checked:?}; unmarkable-by-format: {unmarkable:?}",
        checked.len()
    );
    for expected in ["poly.toml", "rustfmt.toml"] {
        assert!(
            checked.contains(expected),
            "{expected} is a marker-rail scaffold file but the sweep never saw it. Checked: {checked:?}"
        );
    }
}

/// The bound that makes alef's 10-line marker window safe: `inject_hash_line` always writes the
/// stamp on the line after the marker, so the deepest reachable stamp line must stay inside
/// poly's window. Widening `MARKER_SCAN_LINES` to match poly's 11 would break this — the marker
/// would be legal on line 11 and its stamp would land on line 12, outside poly's reach.
#[test]
fn the_deepest_reachable_stamp_line_stays_inside_polys_scan_window() {
    assert!(
        deepest_hash_line() <= POLY_GENERATED_SCAN_LINES,
        "a marker at alef's deepest legal position stamps line {}, past poly's {POLY_GENERATED_SCAN_LINES}-line \
         window: alef would claim files poly still reformats",
        deepest_hash_line()
    );
}