weavatrix-parse 0.3.1

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

#[test]
fn extracts_typed_proto3_rpc_and_streaming_with_exact_spans() {
    let source = concat!(
        "syntax = \"proto3\";\n",
        "package shop.v1;\n",
        "message Outer { message Request {} }\n",
        "message Reply {}\n",
        "service Inventory { rpc Watch(stream Outer.Request) returns (stream Reply); }\n",
    );
    let facts = extract(source);
    assert!(facts.diagnostics.is_empty());
    assert!(
        !facts.contracts.is_empty(),
        "advertised valid proto3 cannot fall back to empty facts"
    );
    assert!(facts.contracts.iter().any(|fact| {
        matches!(
            fact.kind,
            ContractKind::ProtobufRpc {
                ref input,
                ref output,
                client_streaming: true,
                server_streaming: true,
            } if input == "Outer.Request" && output == "Reply"
        ) && &source[fact.span.start..fact.span.end] == "Watch"
    }));
    assert!(facts.contracts.iter().any(|fact| {
        fact.name == "Outer.Request"
            && fact.owner.as_deref() == Some("Outer")
            && fact.kind == ContractKind::ProtobufMessage
    }));
}

#[test]
fn extracts_proto2_and_supported_editions_service_contracts() {
    for (source, option_import) in [
        (
            concat!(
                "syntax = \"proto2\";\n",
                "message Request { optional string id = 1; }\n",
                "message Reply {}\n",
                "service Legacy { rpc Get(Request) returns (Reply); }\n",
            ),
            None,
        ),
        (
            concat!(
                "edition = \"2023\";\n",
                "message Request { string id = 1; }\n",
                "message Reply {}\n",
                "service EditionApi { rpc Get(Request) returns (Reply); }\n",
            ),
            None,
        ),
        (
            concat!(
                "edition = \"2024\";\n",
                "import option \"custom_options.proto\";\n",
                "message Request { string id = 1; }\n",
                "message Reply {}\n",
                "service EditionApi { rpc Get(Request) returns (Reply); }\n",
            ),
            Some("custom_options.proto"),
        ),
    ] {
        let facts = extract(source);
        assert!(facts.diagnostics.is_empty(), "{facts:?}");
        assert!(facts.contracts.iter().any(|fact| {
            matches!(fact.kind, ContractKind::ProtobufRpc { .. }) && fact.name == "Get"
        }));
        if let Some(option_import) = option_import {
            let import = facts
                .imports
                .iter()
                .find(|import| import.specifier == option_import)
                .expect("Edition 2024 option import");
            assert!(!import.reexport);
            assert_eq!(
                source[import.span.start..import.span.end].trim_matches(['"', '\'']),
                option_import
            );
        }
    }
}

#[test]
fn invalid_dialects_fail_closed_with_exact_diagnostics() {
    for source in [
        "syntax = \"proto1\"; message Legacy {}",
        "edition = \"future\"; message Legacy {}",
        "edition = \"2022\"; message Legacy {}",
        "edition = \"2026\"; message Legacy {}",
        "package misplaced; syntax = \"proto3\"; message Legacy {}",
        "syntax = \"proto3\"; edition = \"2023\"; message Legacy {}",
    ] {
        let facts = extract(source);
        assert!(facts.contracts.is_empty());
        assert_eq!(facts.diagnostics.len(), 1);
        assert_eq!(facts.diagnostics[0].code, "protobuf.invalid_dialect");
        let span = facts.diagnostics[0].span;
        assert_eq!((span.line, span.column), (1, 1));
        assert!(!source[span.start..span.end].is_empty());
    }
}

#[test]
fn malformed_supported_dialects_fail_closed() {
    for source in [
        "syntax = \"proto3\"; service Broken {",
        "syntax = \"proto3\"; package bad message X {};",
        "syntax = \"proto3\"; service Bad { rpc Call(Request) (Reply); }",
    ] {
        let facts = extract(source);
        assert!(facts.contracts.is_empty());
        assert_eq!(facts.diagnostics.len(), 1);
        assert_eq!(facts.diagnostics[0].code, "protobuf.syntax_error");
        let span = facts.diagnostics[0].span;
        assert!(
            span.end > span.start,
            "diagnostic must identify the exact offending token"
        );
        assert!(!source[span.start..span.end].is_empty());
    }
}