weavatrix-scan 0.4.6

Deterministic, safe repository scanner for code intelligence
Documentation
#[allow(dead_code)]
mod support;

use std::collections::BTreeSet;
use support::Fixture;
use weavatrix_scan::{
    RepositoryMatch, ScanOptions, Scanner, SelectionDisposition, SelectionMatcher, SkipKind, Walker,
};

#[test]
fn complete_selection_matcher_matches_scanner_manifest() {
    let fixture = Fixture::new("weavatrix-selection-parity");
    fixture.write(".gitignore", "ignored.rs\n");
    fixture.write("ignored.rs", "ignored");
    fixture.write(".hidden.rs", "hidden");
    fixture.write("src/lib.rs", "lib");
    fixture.write("src/readme.txt", "text");
    fixture.write("src/large.rs", "123456789");
    fixture.write("target/generated.rs", "generated");
    fixture.write("target/forced.txt", "forced");
    fixture.write(".private/secret.rs", "secret");
    fixture.write("nested/deep/more/file.rs", "deep");

    let mut options = ScanOptions::default()
        .with_extensions(["rs"])
        .with_override_rules(["target/", "target/forced.txt"])
        .with_skip_hidden(true)
        .with_max_depth(Some(3))
        .metadata_only();
    options.max_file_bytes = 8;

    let report = Scanner::new(&fixture.root)
        .options(options.clone())
        .scan()
        .unwrap();
    let expected = report
        .files
        .iter()
        .map(|file| file.relative.clone())
        .collect::<BTreeSet<_>>();

    let mut matcher = SelectionMatcher::with_options(&fixture.root, &options).unwrap();
    let mut walker = Walker::with_options(&fixture.root, options.walk.with_metadata(true)).unwrap();
    let mut actual = BTreeSet::new();
    while let Some(entry) = walker.next() {
        let entry = entry.unwrap();
        let decision = matcher.matched_entry(&entry).unwrap();
        if decision.is_selected() {
            actual.insert(
                entry
                    .relative_path()
                    .to_string_lossy()
                    .replace(std::path::MAIN_SEPARATOR, "/"),
            );
        }
        if matches!(decision.disposition(), SelectionDisposition::Skipped(_)) && entry.is_dir() {
            walker.skip_current_dir();
        }
    }
    assert_eq!(actual, expected);

    assert_eq!(
        matcher.matched("ignored.rs").unwrap().skip_kind(),
        Some(SkipKind::Override)
    );
    let forced = matcher.matched("target/forced.txt").unwrap();
    assert!(forced.is_selected());
    assert_eq!(forced.repository_match(), RepositoryMatch::OverrideInclude);
    assert!(
        matcher
            .clone()
            .matched("target/forced.txt")
            .unwrap()
            .is_selected()
    );
    assert_eq!(
        matcher
            .matched("nested/deep/more/file.rs")
            .unwrap()
            .skip_kind(),
        Some(SkipKind::MaxDepth)
    );
}

#[test]
fn standalone_matching_applies_ancestor_and_file_policies() {
    let fixture = Fixture::new("weavatrix-selection-standalone");
    fixture.write(".gitignore", "ignored.rs\n");
    fixture.write("ignored.rs", "ignored");
    fixture.write(".hidden.rs", "hidden");
    fixture.write("src/readme.txt", "text");
    fixture.write("src/large.rs", "123456789");
    fixture.write("target/generated.rs", "generated");
    fixture.write(".private/secret.rs", "secret");
    let mut path_options = ScanOptions::default()
        .with_extensions(["rs"])
        .with_skip_hidden(true)
        .with_max_depth(Some(3))
        .metadata_only();
    path_options.max_file_bytes = 8;
    let mut ignore_only = SelectionMatcher::with_options(&fixture.root, &path_options).unwrap();
    assert_eq!(
        ignore_only.matched("ignored.rs").unwrap().skip_kind(),
        Some(SkipKind::Ignored)
    );
    assert_eq!(
        ignore_only.matched(".hidden.rs").unwrap().skip_kind(),
        Some(SkipKind::Hidden)
    );
    assert_eq!(
        ignore_only.matched("src/readme.txt").unwrap().skip_kind(),
        Some(SkipKind::Extension)
    );
    assert_eq!(
        ignore_only.matched("src/large.rs").unwrap().skip_kind(),
        Some(SkipKind::Oversized)
    );
    assert_eq!(
        ignore_only
            .matched("target/generated.rs")
            .unwrap()
            .skip_kind(),
        Some(SkipKind::StandardDirectory)
    );
    assert_eq!(
        ignore_only
            .matched(".private/secret.rs")
            .unwrap()
            .skip_kind(),
        Some(SkipKind::Hidden)
    );
}

#[test]
fn matcher_normalization_is_lossless_and_rejects_escape() {
    let fixture = Fixture::new("weavatrix-selection-normalize");
    fixture.write("src/lib.rs", "lib");
    let repository = weavatrix_scan::RepositoryMatcher::new(&fixture.root).unwrap();

    assert_eq!(
        repository.normalize("src/lib.rs").unwrap(),
        std::path::PathBuf::from("src/lib.rs")
    );
    assert!(repository.normalize("../outside.rs").is_err());
    assert!(
        repository
            .normalize(fixture.root.join("..").join("outside.rs"))
            .is_err()
    );
}