rd-ast 0.0.1

Canonical, producer-agnostic AST for R's Rd (documentation) format
Documentation
use super::*;
use crate::{RdMethodKind, RdNode, RdPath, RdPathSegment, RdTag, producer};

fn base() -> RdPath {
    RdPath::new(vec![RdPathSegment::TopLevel(4), RdPathSegment::Child(2)])
}
fn group(nodes: Vec<RdNode>) -> RdNode {
    RdNode::group(nodes)
}
fn method(tag: RdTag, generic: Vec<RdNode>, qualifier: Vec<RdNode>) -> RdNode {
    RdNode::tagged(tag, None, vec![group(generic), group(qualifier)])
}
fn text(value: &str) -> RdNode {
    RdNode::Text(value.into())
}

#[test]
fn method_kinds_and_exact_scalars() {
    for (tag, kind) in [
        (RdTag::Method, RdMethodKind::Method),
        (RdTag::S3Method, RdMethodKind::S3Method),
        (RdTag::S4Method, RdMethodKind::S4Method),
    ] {
        let node = method(tag, vec![text("print")], vec![text("data.frame")]);
        let view = node.inspect_method(&base()).unwrap().unwrap();
        assert_eq!(view.kind(), kind);
        assert_eq!(view.generic(), "print");
        assert_eq!(view.qualifier(), "data.frame");
    }
    for value in ["[", "+", "%>%", "OldClass,NewClass"] {
        let node = method(RdTag::S4Method, vec![text(value)], vec![text(value)]);
        assert_eq!(
            node.inspect_method(&base()).unwrap().unwrap().generic(),
            value
        );
    }
    let node = method(
        RdTag::Method,
        vec![text("pr"), text("int")],
        vec![text("data."), text("frame")],
    );
    let view = node.inspect_method(&base()).unwrap().unwrap();
    assert_eq!(view.generic(), "print");
    assert_eq!(view.qualifier(), "data.frame");
}

#[test]
fn method_rejects_options_arity_groups_and_non_text() {
    let option = RdNode::tagged(RdTag::Method, Some(vec![]), vec![]);
    assert!(matches!(
        option.inspect_method(&base()).unwrap_err().kind(),
        RdShapeErrorKind::UnexpectedOption
    ));
    for count in [0, 1, 3] {
        let node = RdNode::tagged(
            RdTag::Method,
            None,
            (0..count).map(|_| group(vec![])).collect(),
        );
        assert!(
            matches!(node.inspect_method(&base()).unwrap_err().kind(), RdShapeErrorKind::WrongArity { actual, .. } if *actual == count)
        );
    }
    for index in 0..2 {
        let mut children = vec![group(vec![]), group(vec![])];
        children[index] = RdNode::Text("bad".into());
        let error = RdNode::tagged(RdTag::Method, None, children)
            .inspect_method(&base())
            .unwrap_err();
        assert_eq!(error.path(), &base().with_child(index));
        assert!(matches!(
            error.kind(),
            RdShapeErrorKind::UnexpectedNode {
                expected: RdExpectedNode::Group,
                ..
            }
        ));
    }
    for (index, bad) in [
        (0, RdNode::RCode("x".into())),
        (1, RdNode::Verb("x".into())),
        (0, RdNode::tagged(RdTag::Emph, None, vec![])),
    ] {
        let mut children = vec![group(vec![]), group(vec![])];
        children[index] = group(vec![bad]);
        let error = RdNode::tagged(RdTag::Method, None, children)
            .inspect_method(&base())
            .unwrap_err();
        assert_eq!(error.path(), &base().with_child(index).with_child(0));
        assert!(matches!(
            error.kind(),
            RdShapeErrorKind::UnexpectedContent { .. }
        ));
    }
}

#[test]
fn method_raw_matching_and_unrelated_nodes() {
    for tag in [r"\method", r"\S3method", r"\S4method"] {
        let raw = RdNode::Raw(producer::raw_node(
            Some(tag.into()),
            None,
            vec![],
            None,
            vec![],
        ));
        assert!(matches!(
            raw.inspect_method(&base()).unwrap_err().kind(),
            RdShapeErrorKind::UnexpectedNode {
                expected: RdExpectedNode::Tagged,
                actual: RdNodeKind::Raw
            }
        ));
    }
    let other = RdNode::Raw(producer::raw_node(
        Some(r"\if".into()),
        None,
        vec![],
        None,
        vec![],
    ));
    assert_eq!(other.inspect_method(&base()).unwrap(), None);
}