use crate::parser::tests::cst::{ExpectedNode, assert_equivalent};
use crate::{SyntaxKind, parse};
#[test]
fn knot_double_eq_trailing() {
assert_equivalent(
parse("== myKnot ==\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_triple_eq_trailing() {
assert_equivalent(
parse("=== myKnot ===\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_quad_eq_trailing() {
assert_equivalent(
parse("==== myKnot ====\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_no_trailing_eq() {
assert_equivalent(
parse("== myKnot\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_triple_eq_no_trailing() {
assert_equivalent(
parse("=== myKnot\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_asymmetric_eq() {
assert_equivalent(
parse("== myKnot ===\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn function_knot_basic() {
assert_equivalent(
parse("== function greet ==\nHi!\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn function_knot_no_trailing_eq() {
assert_equivalent(
parse("== function greet\nHi!\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn function_knot_triple_eq() {
assert_equivalent(
parse("=== function greet ===\nHi!\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_single_param() {
assert_equivalent(
parse("== greet(name) ==\nHi.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_two_params() {
assert_equivalent(
parse("== greet(a, b) ==\nHi.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_three_params() {
assert_equivalent(
parse("== greet(a, b, c) ==\nHi.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
KNOT_PARAM_DECL {
IDENTIFIER
}
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_empty_parens() {
assert_equivalent(
parse("== greet() ==\nHi.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_ref_param() {
assert_equivalent(
parse("== modify(ref x) ==\nDone.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_mixed_ref_params() {
assert_equivalent(
parse("== modify(ref x, y) ==\nDone.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_all_ref_params() {
assert_equivalent(
parse("== modify(ref x, ref y) ==\nDone.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_divert_param() {
assert_equivalent(
parse("== f(-> target) ==\nDone.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [DIVERT] {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn knot_divert_ref_param() {
assert_equivalent(
parse("== f(-> ref x) ==\nDone.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [DIVERT, KW_REF] {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn function_knot_with_param() {
assert_equivalent(
parse("== function greet(name) ==\nHi!\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn function_knot_with_ref_param() {
assert_equivalent(
parse("== function greet(ref name) ==\nHi!\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn function_knot_mixed_params() {
assert_equivalent(
parse("== function f(a, ref b, -> c) ==\nDone.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
KNOT_PARAM_DECL [DIVERT] {
IDENTIFIER
}
}
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn stitch_basic() {
assert_equivalent(
parse("== k ==\n= s\nContent.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_with_params() {
assert_equivalent(
parse("== k ==\n= s(x)\nContent.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_with_ref_param() {
assert_equivalent(
parse("== k ==\n= s(ref x)\nContent.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL [KW_REF] {
IDENTIFIER
}
}
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_multiple() {
assert_equivalent(
parse("== k ==\n= s1\nA.\n= s2\nB.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_multiple_with_params() {
assert_equivalent(
parse("== k ==\n= s1(x)\nA.\n= s2(y)\nB.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
KNOT_PARAMS {
KNOT_PARAM_DECL {
IDENTIFIER
}
}
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_content_before_first() {
assert_equivalent(
parse("== k ==\nBefore.\n= s\nIn stitch.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_three() {
assert_equivalent(
parse("== k ==\n= s1\nA.\n= s2\nB.\n= s3\nC.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn stitch_multi_line_body() {
assert_equivalent(
parse("== k ==\n= s\nLine one.\nLine two.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn body_plain_content() {
assert_equivalent(
parse("== k ==\nHello.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn body_multiple_content_lines() {
assert_equivalent(
parse("== k ==\nLine one.\nLine two.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn body_divert() {
assert_equivalent(
parse("== k ==\n-> target\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
DIVERT_NODE {
SIMPLE_DIVERT {
DIVERT_TARGET_WITH_ARGS {
PATH
}
}
}
}
}
}
}),
);
}
#[test]
fn body_var_decl() {
assert_equivalent(
parse("== k ==\nVAR x = 5\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
VAR_DECL {
IDENTIFIER
INTEGER_LIT
}
}
}
}),
);
}
#[test]
fn body_const_decl() {
assert_equivalent(
parse("== k ==\nCONST x = 5\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONST_DECL {
IDENTIFIER
INTEGER_LIT
}
}
}
}),
);
}
#[test]
fn body_logic_return() {
assert_equivalent(
parse("== function f ==\n~ return 5\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER [KW_FUNCTION] {
IDENTIFIER
}
KNOT_BODY {
LOGIC_LINE {
RETURN_STMT {
INTEGER_LIT
}
}
}
}
}),
);
}
#[test]
fn boundary_two_consecutive_knots() {
assert_equivalent(
parse("== k1 ==\nA.\n== k2 ==\nB.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn boundary_knot_terminates_at_external() {
assert_equivalent(
parse("== k ==\nA.\nEXTERNAL greet()\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
EXTERNAL_DECL {
IDENTIFIER
}
}),
);
}
#[test]
fn boundary_knot_terminates_at_include() {
assert_equivalent(
parse("== k ==\nA.\nINCLUDE other.ink\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
INCLUDE_STMT {
FILE_PATH
}
}),
);
}
#[test]
fn boundary_stitch_ends_at_next_stitch() {
assert_equivalent(
parse("== k ==\n= s1\nA.\n= s2\nB.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
}),
);
}
#[test]
fn boundary_stitch_ends_at_next_knot() {
assert_equivalent(
parse("== k1 ==\n= s\nA.\n== k2 ==\nB.\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}),
);
}
#[test]
fn boundary_stitch_terminates_at_external() {
assert_equivalent(
parse("== k ==\n= s\nA.\nEXTERNAL greet()\n"),
cst!(SOURCE_FILE {
KNOT_DEF {
KNOT_HEADER {
IDENTIFIER
}
KNOT_BODY {
STITCH_DEF {
STITCH_HEADER {
IDENTIFIER
}
STITCH_BODY {
CONTENT_LINE {
MIXED_CONTENT {
TEXT
}
}
}
}
}
}
EXTERNAL_DECL {
IDENTIFIER
}
}),
);
}
fn assert_knot_uniformity(src: &str) {
let p = parse(src);
assert!(p.errors().is_empty(), "unexpected errors: {:?}", p.errors());
for node in p.syntax().descendants() {
if node.kind() == SyntaxKind::KNOT_DEF {
let headers: Vec<_> = node
.children()
.filter(|c| c.kind() == SyntaxKind::KNOT_HEADER)
.collect();
let bodies: Vec<_> = node
.children()
.filter(|c| c.kind() == SyntaxKind::KNOT_BODY)
.collect();
assert_eq!(
headers.len(),
1,
"KNOT_DEF should have exactly 1 KNOT_HEADER, found {} in `{src}`",
headers.len(),
);
assert_eq!(
bodies.len(),
1,
"KNOT_DEF should have exactly 1 KNOT_BODY, found {} in `{src}`",
bodies.len(),
);
let header_start = headers[0].text_range().start();
let body_start = bodies[0].text_range().start();
assert!(
header_start < body_start,
"KNOT_HEADER ({header_start:?}) should come before KNOT_BODY ({body_start:?}) in `{src}`",
);
}
if node.kind() == SyntaxKind::STITCH_DEF {
let headers: Vec<_> = node
.children()
.filter(|c| c.kind() == SyntaxKind::STITCH_HEADER)
.collect();
let bodies: Vec<_> = node
.children()
.filter(|c| c.kind() == SyntaxKind::STITCH_BODY)
.collect();
assert_eq!(
headers.len(),
1,
"STITCH_DEF should have exactly 1 STITCH_HEADER, found {} in `{src}`",
headers.len(),
);
assert_eq!(
bodies.len(),
1,
"STITCH_DEF should have exactly 1 STITCH_BODY, found {} in `{src}`",
bodies.len(),
);
let header_start = headers[0].text_range().start();
let body_start = bodies[0].text_range().start();
assert!(
header_start < body_start,
"STITCH_HEADER ({header_start:?}) should come before STITCH_BODY ({body_start:?}) in `{src}`",
);
}
}
}
#[test]
fn uniformity_basic_knot() {
assert_knot_uniformity("== myKnot ==\nHello.\n");
}
#[test]
fn uniformity_function_knot() {
assert_knot_uniformity("== function greet ==\nHi!\n");
}
#[test]
fn uniformity_knot_with_params() {
assert_knot_uniformity("== greet(name, ref title) ==\nHi.\n");
}
#[test]
fn uniformity_knot_with_stitch() {
assert_knot_uniformity("== k ==\n= s\nContent.\n");
}
#[test]
fn uniformity_knot_with_multiple_stitches() {
assert_knot_uniformity("== k ==\n= s1\nA.\n= s2\nB.\n= s3\nC.\n");
}
#[test]
fn uniformity_consecutive_knots() {
assert_knot_uniformity("== k1 ==\nA.\n== k2 ==\nB.\n");
}
#[test]
fn uniformity_knot_with_inline_decls() {
assert_knot_uniformity("== k ==\nVAR x = 5\nCONST y = 10\nHello.\n");
}
#[test]
fn uniformity_stitch_with_params() {
assert_knot_uniformity("== k ==\n= s(x, ref y)\nContent.\n");
}
#[test]
fn has_knot_def_not_stitch_def_at_top() {
let p = parse("== myKnot ==\nHello.\n");
let root = p.syntax();
let has_knot = root.children().any(|n| n.kind() == SyntaxKind::KNOT_DEF);
let has_stitch_at_top = root.children().any(|n| n.kind() == SyntaxKind::STITCH_DEF);
assert!(has_knot, "top level must have KNOT_DEF");
assert!(!has_stitch_at_top, "top level must not have STITCH_DEF");
}
#[test]
fn stitch_inside_knot_body_not_top_level() {
let p = parse("== k ==\n= s\nContent.\n");
let root = p.syntax();
let has_stitch_at_top = root.children().any(|n| n.kind() == SyntaxKind::STITCH_DEF);
assert!(
!has_stitch_at_top,
"STITCH_DEF must not be a SOURCE_FILE child"
);
let stitch = root
.descendants()
.find(|n| n.kind() == SyntaxKind::STITCH_DEF)
.expect("expected STITCH_DEF");
let parent = stitch.parent().expect("STITCH_DEF has no parent");
assert_eq!(
parent.kind(),
SyntaxKind::KNOT_BODY,
"STITCH_DEF should be inside KNOT_BODY, found in {:?}",
parent.kind(),
);
}
#[test]
fn function_no_parens_has_no_knot_params() {
let p = parse("== function greet ==\nHi!\n");
let root = p.syntax();
let header = root
.descendants()
.find(|n| n.kind() == SyntaxKind::KNOT_HEADER)
.expect("expected KNOT_HEADER");
let has_params = header
.children()
.any(|c| c.kind() == SyntaxKind::KNOT_PARAMS);
assert!(
!has_params,
"KNOT_HEADER without parens must not have KNOT_PARAMS"
);
}
#[test]
fn function_with_parens_has_knot_params() {
let p = parse("== function greet(name) ==\nHi!\n");
let root = p.syntax();
let header = root
.descendants()
.find(|n| n.kind() == SyntaxKind::KNOT_HEADER)
.expect("expected KNOT_HEADER");
let has_params = header
.children()
.any(|c| c.kind() == SyntaxKind::KNOT_PARAMS);
assert!(has_params, "KNOT_HEADER with parens must have KNOT_PARAMS");
}
#[test]
fn param_decl_inside_knot_params() {
let p = parse("== greet(name) ==\nHi.\n");
let root = p.syntax();
let param_decl = root
.descendants()
.find(|n| n.kind() == SyntaxKind::KNOT_PARAM_DECL)
.expect("expected KNOT_PARAM_DECL");
let parent = param_decl.parent().expect("KNOT_PARAM_DECL has no parent");
assert_eq!(
parent.kind(),
SyntaxKind::KNOT_PARAMS,
"KNOT_PARAM_DECL should be inside KNOT_PARAMS, found in {:?}",
parent.kind(),
);
}
#[test]
fn inline_var_in_knot_body_not_source_file() {
let p = parse("== k ==\nVAR x = 5\n");
let root = p.syntax();
let var_decl = root
.descendants()
.find(|n| n.kind() == SyntaxKind::VAR_DECL)
.expect("expected VAR_DECL");
let parent = var_decl.parent().expect("VAR_DECL has no parent");
assert_eq!(
parent.kind(),
SyntaxKind::KNOT_BODY,
"VAR_DECL should be inside KNOT_BODY, found in {:?}",
parent.kind(),
);
let has_var_at_top = root.children().any(|n| n.kind() == SyntaxKind::VAR_DECL);
assert!(
!has_var_at_top,
"VAR_DECL inside knot must not be a SOURCE_FILE child"
);
}
#[test]
fn external_not_in_knot_body() {
let p = parse("== k ==\nA.\nEXTERNAL greet()\n");
let root = p.syntax();
let has_external_at_top = root
.children()
.any(|n| n.kind() == SyntaxKind::EXTERNAL_DECL);
assert!(
has_external_at_top,
"EXTERNAL_DECL must be a SOURCE_FILE child"
);
let knot_body = root
.descendants()
.find(|n| n.kind() == SyntaxKind::KNOT_BODY)
.expect("expected KNOT_BODY");
let has_external_in_body = knot_body
.descendants()
.any(|n| n.kind() == SyntaxKind::EXTERNAL_DECL);
assert!(
!has_external_in_body,
"EXTERNAL_DECL must not be inside KNOT_BODY",
);
}
#[test]
fn stitch_params_in_stitch_header() {
let p = parse("== k ==\n= s(x)\nContent.\n");
let root = p.syntax();
let stitch_header = root
.descendants()
.find(|n| n.kind() == SyntaxKind::STITCH_HEADER)
.expect("expected STITCH_HEADER");
let has_params = stitch_header
.children()
.any(|c| c.kind() == SyntaxKind::KNOT_PARAMS);
assert!(
has_params,
"STITCH_HEADER with parens must have KNOT_PARAMS"
);
}
#[test]
fn error_missing_identifier() {
let src = "== ==\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for missing identifier in knot header",
);
}
#[test]
fn error_unclosed_params() {
let src = "== myKnot(\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for unclosed params in knot header",
);
}
#[test]
fn error_ref_no_identifier() {
let src = "== myKnot(ref ) ==\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for ref with no identifier",
);
}
#[test]
fn no_trailing_newline_lossless() {
let src = "== myKnot ==";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
}
#[test]
fn empty_knot_body() {
let src = "== k1 ==\n== k2 ==\nContent.\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let knot_bodies: Vec<_> = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::KNOT_BODY)
.collect();
assert_eq!(knot_bodies.len(), 2, "should have 2 knot bodies");
let first_body_children: Vec<_> = knot_bodies[0].children().collect();
assert!(
first_body_children.is_empty(),
"first knot body should be empty, found: {:?}",
first_body_children
.iter()
.map(crate::SyntaxNode::kind)
.collect::<Vec<_>>(),
);
}