sysml-v2-parser 0.22.0

SysML v2 textual notation parser for Rust
Documentation
//! AST shape tests for Spec42 diagnostics parser roadmap items.

use std::fs;
use std::path::PathBuf;

use sysml_v2_parser::ast::*;
use sysml_v2_parser::parse;

fn fixture(name: &str) -> String {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fixtures")
        .join(name);
    fs::read_to_string(path).expect("read fixture")
}

fn first_package<'a>(root: &'a RootNamespace) -> &'a Package {
    match &root.elements[0].value {
        RootElement::Package(p) => &p.value,
        other => panic!("expected package, got {other:?}"),
    }
}

fn package_body_elements(pkg: &Package) -> &[Node<PackageBodyElement>] {
    match &pkg.body {
        PackageBody::Brace { elements } => elements.as_slice(),
        _ => panic!("expected brace package body"),
    }
}

#[test]
fn transition_accept_retained_with_spans() {
    let root = parse(&fixture("transition-accept-typed.sysml")).expect("parse");
    let pkg = first_package(&root);
    let state_def = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::StateDef(sd) => &sd.value,
        other => panic!("expected state def, got {other:?}"),
    };
    let transitions: Vec<&Transition> = match &state_def.body {
        StateDefBody::Brace { elements } => elements
            .iter()
            .filter_map(|e| match &e.value {
                StateDefBodyElement::Transition(t) => Some(&t.value),
                _ => None,
            })
            .collect(),
        _ => panic!("expected brace state body"),
    };
    assert_eq!(transitions.len(), 2);
    assert!(transitions[0].is_initial);
    let accept = transitions[0]
        .accept
        .as_ref()
        .expect("shorthand accept");
    match accept {
        TransitionAccept::Shorthand(expr) => {
            assert!(matches!(expr.value, Expression::FeatureRef(ref n) if n == "StartPressed"));
            assert!(expr.span.len > 0);
        }
        _ => panic!("expected shorthand accept"),
    }
    let typed = transitions[1]
        .accept
        .as_ref()
        .expect("typed accept");
    match typed {
        TransitionAccept::Payload(p) => {
            assert_eq!(p.name, "evt");
            assert_eq!(p.type_name.as_deref(), Some("StartEvent"));
            assert!(p.name_span.len > 0);
            assert!(p.type_span.is_some());
        }
        _ => panic!("expected typed accept"),
    }
}

#[test]
fn final_state_members_parsed() {
    let root = parse(&fixture("final-state.sysml")).expect("parse");
    let pkg = first_package(&root);
    let state_def = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::StateDef(sd) => &sd.value,
        other => panic!("expected state def, got {other:?}"),
    };
    let finals: Vec<&FinalState> = match &state_def.body {
        StateDefBody::Brace { elements } => elements
            .iter()
            .filter_map(|e| match &e.value {
                StateDefBodyElement::FinalState(f) => Some(&f.value),
                _ => None,
            })
            .collect(),
        _ => panic!("expected brace state body"),
    };
    assert_eq!(finals.len(), 2);
    assert_eq!(finals[0].state_name, "expired");
    assert_eq!(finals[1].state_name, "completed");
    assert!(finals[0].name_span.len > 0);
}

#[test]
fn send_payload_on_control_node_action() {
    let root = parse(&fixture("send-payload.sysml")).expect("parse");
    let pkg = first_package(&root);
    let action_def = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::ActionDef(ad) => &ad.value,
        other => panic!("expected action def, got {other:?}"),
    };
    let send_usage = match &action_def.body {
        ActionDefBody::Brace { elements } => elements
            .iter()
            .find_map(|e| match &e.value {
                ActionDefBodyElement::ActionUsage(a) => Some(&a.value),
                _ => None,
            })
            .expect("send action usage"),
        _ => panic!("expected brace action body"),
    };
    assert_eq!(send_usage.name, "send");
    let send = send_usage.send.as_ref().expect("send payload");
    assert_eq!(send.name, "message");
    assert_eq!(send.type_name.as_deref(), Some("AlertMessage"));
    assert!(send.name_span.len > 0);
    assert!(send.type_span.is_some());
}

#[test]
fn viewpoint_stakeholder_and_purpose_members() {
    let root = parse(&fixture("viewpoint-stakeholder-purpose.sysml")).expect("parse");
    let pkg = first_package(&root);
    let vp = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::ViewpointDef(v) => &v.value,
        other => panic!("expected viewpoint def, got {other:?}"),
    };
    let body = match &vp.body {
        RequirementDefBody::Brace { elements } => elements,
        _ => panic!("expected brace viewpoint body"),
    };
    assert!(body.iter().any(|e| matches!(
        e.value,
        RequirementDefBodyElement::Stakeholder(_)
    )));
    assert!(body.iter().any(|e| matches!(
        e.value,
        RequirementDefBodyElement::Purpose(_)
    )));
}

#[test]
fn metadata_keyword_usage_in_part_body() {
    let root = parse(&fixture("metadata-keyword-usage.sysml")).expect("parse");
    let pkg = first_package(&root);
    let part_def = package_body_elements(pkg)
        .iter()
        .find_map(|e| match &e.value {
            PackageBodyElement::PartDef(pd) => Some(&pd.value),
            _ => None,
        })
        .expect("part def");
    let keyword = match &part_def.body {
        PartDefBody::Brace { elements } => elements
            .iter()
            .find_map(|e| match &e.value {
                PartDefBodyElement::MetadataKeywordUsage(k) => Some(&k.value),
                _ => None,
            })
            .expect("metadata keyword usage"),
        _ => panic!("expected brace part body"),
    };
    assert_eq!(keyword.keyword, "Tag");
    assert!(keyword.keyword_span.len > 0);
}

#[test]
fn verification_local_attribute_has_name_span() {
    let root = parse(&fixture("verification-local-attribute.sysml")).expect("parse");
    let pkg = first_package(&root);
    let verification = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::VerificationCaseDef(v) => &v.value,
        other => panic!("expected verification def, got {other:?}"),
    };
    let attr = match &verification.body {
        UseCaseDefBody::Brace { elements } => elements
            .iter()
            .find_map(|e| match &e.value {
                UseCaseDefBodyElement::AttributeDef(a) => Some(&a.value),
                _ => None,
            })
            .expect("attribute def"),
        _ => panic!("expected brace verification body"),
    };
    assert_eq!(attr.name, "count");
    assert!(attr.name_span.is_some());
    assert_eq!(attr.typing.as_deref(), Some("Integer"));
}

#[test]
fn requirement_body_rep_language_parsed() {
    let root = parse(&fixture("requirement-rep-language.sysml")).expect("parse");
    let pkg = first_package(&root);
    let req = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::RequirementDef(r) => &r.value,
        other => panic!("expected requirement def, got {other:?}"),
    };
    let rep = match &req.body {
        RequirementDefBody::Brace { elements } => elements
            .iter()
            .find_map(|e| match &e.value {
                RequirementDefBodyElement::TextualRep(t) => Some(&t.value),
                _ => None,
            })
            .expect("textual rep"),
        _ => panic!("expected brace requirement body"),
    };
    assert_eq!(rep.language, "sysml");
    assert!(rep.language_span.is_some());
}

#[test]
fn diagnostic_catalog_documents_stable_codes() {
    use sysml_v2_parser::parser::diagnostic_catalog::DOCUMENTED_CODES;
    assert!(DOCUMENTED_CODES.contains(&"missing_member_name"));
    assert!(DOCUMENTED_CODES.contains(&"missing_closing_brace"));
}

#[test]
fn transition_first_sets_is_initial_flag() {
    let input = "package P { state def S { transition t first idle then running; } }";
    let root = parse(input).expect("parse");
    let pkg = first_package(&root);
    let state_def = match &package_body_elements(pkg)[0].value {
        PackageBodyElement::StateDef(sd) => &sd.value,
        _ => panic!("expected state def"),
    };
    let transition = match &state_def.body {
        StateDefBody::Brace { elements } => elements
            .iter()
            .find_map(|e| match &e.value {
                StateDefBodyElement::Transition(t) => Some(&t.value),
                _ => None,
            })
            .expect("transition"),
        _ => panic!("expected brace body"),
    };
    assert!(transition.is_initial);
    assert!(transition.source.is_some());
}