weavatrix-parse 0.3.3

Dependency-free source tokenizer and structural extractor for repository intelligence
Documentation
use super::*;

#[test]
fn go_groups_imports_and_capitalisation_marks_export() {
    let source = "package main\n\nimport (\n\tf \"fmt\"\n\t\"edgehawk.com/app/reader\"\n)\n\n\
         func Exported() {}\nfunc internal() {}\n";
    let facts = extract(source, Language::Go);
    assert_eq!(
        facts
            .imports
            .iter()
            .map(|import| import.specifier.as_str())
            .collect::<Vec<_>>(),
        ["fmt", "edgehawk.com/app/reader"],
        "a grouped import block yields one fact per path"
    );
    assert_eq!(
        facts.imports[0].bindings,
        [ImportBinding {
            imported: "fmt".to_owned(),
            local: "f".to_owned(),
        }]
    );
    assert_eq!(
        facts.imports[1].bindings,
        [ImportBinding {
            imported: "reader".to_owned(),
            local: "reader".to_owned(),
        }]
    );
    let items = facts
        .declarations
        .iter()
        .map(|item| (item.name.as_str(), item.exported))
        .collect::<Vec<_>>();
    assert!(items.contains(&("Exported", true)), "got {items:?}");
    assert!(items.contains(&("internal", false)), "got {items:?}");
}

#[test]
fn exported_function_extent_includes_its_body() {
    let source = "package main\n\nfunc CanDelete(viewer bool) bool {\n\treturn !viewer\n}\n";
    let facts = extract(source, Language::Go);
    let declaration = facts
        .declarations
        .iter()
        .find(|item| item.name == "CanDelete")
        .expect("the exported function is declared");

    assert_eq!(
        &source[declaration.extent.start..declaration.extent.end],
        "func CanDelete(viewer bool) bool {\n\treturn !viewer\n}"
    );
}

#[test]
fn go_const_and_var_groups_declare_each_line() {
    let source = r#"package main
const (
EventAdd = "added"
eventDelete = "deleted"
)
var (
endpoint = flag.String("endpoint", "/events", "endpoint")
topics = []string{EventAdd, eventDelete}
)
"#;
    let facts = extract(source, Language::Go);
    let items = facts
        .declarations
        .iter()
        .map(|item| (item.name.as_str(), item.kind, item.span.line))
        .collect::<Vec<_>>();
    for expected in [
        ("EventAdd", DeclarationKind::Constant, 3),
        ("eventDelete", DeclarationKind::Constant, 4),
        ("endpoint", DeclarationKind::Variable, 7),
        ("topics", DeclarationKind::Variable, 8),
    ] {
        assert!(
            items.contains(&expected),
            "missing {expected:?}; got {items:?}"
        );
    }
}

#[test]
fn go_grouped_initializers_keep_their_call_references() {
    let facts = extract(
        "package main\nvar (\n flagName = config.String(\"name\")\n)\n",
        Language::Go,
    );
    assert!(
        facts
            .declarations
            .iter()
            .any(|item| item.name == "flagName" && item.kind == DeclarationKind::Variable),
        "the grouped declaration must survive"
    );
    assert!(
        facts.references.iter().any(|reference| {
            reference.name == "String"
                && reference.kind == ReferenceKind::Call
                && reference.receiver.as_deref() == Some("config")
        }),
        "the initializer call must survive, got {:?}",
        facts.references
    );
}

#[test]
fn go_grouped_values_do_not_turn_continuation_lines_into_declarations() {
    let source = r#"package main
var (
config = Config{
    Name: "primary",
}
continued =
    buildValue
next = 1
)
"#;
    let facts = extract(source, Language::Go);
    let names = facts
        .declarations
        .iter()
        .map(|item| item.name.as_str())
        .collect::<Vec<_>>();
    for expected in ["config", "continued", "next"] {
        assert!(
            names.contains(&expected),
            "missing {expected}; got {names:?}"
        );
    }
    for false_positive in ["Name", "buildValue"] {
        assert!(
            !names.contains(&false_positive),
            "{false_positive} is an initializer expression, got {names:?}"
        );
    }
}

#[test]
fn a_large_go_group_does_not_swallow_following_functions() {
    use std::fmt::Write as _;

    let mut source = String::from("package main\nvar (\n");
    for index in 0..1_100 {
        let _ = writeln!(source, "value{index} = {index}");
    }
    source.push_str(")\nfunc AfterGroup() {}\n");
    let facts = extract(&source, Language::Go);
    assert!(
        facts
            .declarations
            .iter()
            .any(|item| item.name == "AfterGroup" && item.kind == DeclarationKind::Function),
        "the closing group delimiter must return scanning to the following function"
    );
}