use super::*;
#[test]
fn minimal_flow_decl() {
let p = assert_lossless("flow greet() {\n}\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn flow_with_prose_body() {
let p = assert_lossless("flow greet(name) {\n Hello, {name}! <>\n}\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn fn_decl_parses() {
let p = assert_lossless("fn heal(hp) {\n var x = hp + 1\n}\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn use_and_import_and_module() {
let src = "use story::npcs::{guard, merchant as trader};\nimport story::items\nmodule inner {\n var secret = 1\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn use_decl_semicolon_is_consumed_by_the_decl_not_left_as_prose() {
let src = "use a::b;\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let use_decl = p
.syntax()
.children()
.find(|n| n.kind() == SyntaxKind::USE_DECL)
.expect("USE_DECL");
assert!(
use_decl
.children_with_tokens()
.any(|t| t.kind() == SyntaxKind::SEMICOLON),
"expected the `;` inside USE_DECL, tree: {use_decl:#?}"
);
assert_eq!(p.syntax().children().count(), 1);
}
#[test]
fn use_decl_without_semicolon_still_parses() {
let src = "use a::b\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn var_const_flags_struct_extern() {
let src = "var hp = 10\nconst MAX = 100\nflags Mood = (calm), wary, hostile\nstruct Item {\n name: string,\n weight: int\n}\nextern log(msg)\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn nested_stitch_flow() {
let src = "flow garden() {\n flow gate() {\n Creak.\n }\n -> gate\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn leading_doc_comment_attaches_as_flow_decls_first_child() {
let src = "/// Greets the player.\nflow greet() {\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl");
let first_child = flow
.syntax()
.children()
.next()
.expect("flow decl has a child node");
assert_eq!(first_child.kind(), SyntaxKind::DOC_COMMENT);
assert_eq!(first_child.text(), "/// Greets the player.\n");
}
#[test]
fn multiline_leading_doc_comment_is_one_doc_comment_node() {
let src = "/// Line one.\n/// Line two.\nflow greet() {\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl");
assert_eq!(count_node_kind(flow.syntax(), SyntaxKind::DOC_COMMENT), 1);
let doc = flow.doc().expect("doc attached");
assert_eq!(doc.lines().len(), 2);
}
#[test]
fn leading_doc_comment_attaches_to_var_const_flags_struct_extern() {
for (src, kind) in [
("/// x\nvar x = 1\n", SyntaxKind::VAR_DECL),
("/// x\nconst x = 1\n", SyntaxKind::CONST_DECL),
("/// x\nflags F = a, b\n", SyntaxKind::FLAGS_DECL),
("/// x\nstruct S { f: int }\n", SyntaxKind::STRUCT_DECL),
("/// x\nextern e(a)\n", SyntaxKind::EXTERN_DECL),
] {
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "{src:?} errors: {:?}", p.errors());
let decl = p.syntax().children().find(|n| n.kind() == kind);
assert!(decl.is_some(), "{kind:?} not found in {src:?}");
let decl = decl.expect("checked above");
let first_child = decl.children().next().expect("decl has a child node");
assert_eq!(
first_child.kind(),
SyntaxKind::DOC_COMMENT,
"{src:?}: doc did not attach as leading child"
);
}
}
#[test]
fn blank_line_breaks_the_leading_doc_run_unattached_falls_back_to_bare_tokens() {
let src = "/// orphaned\n\nflow greet() {\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::DOC_COMMENT));
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl still parses");
assert!(flow.doc().is_none());
}
#[test]
fn doc_comment_with_no_following_declaration_falls_back_to_bare_tokens() {
let src = "/// just some prose after this, no decl\nHello there.\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::DOC_COMMENT));
}
#[test]
fn plain_comment_between_doc_lines_breaks_the_run() {
let src = "/// kept\n// not a doc line\nflow greet() {\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::DOC_COMMENT));
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl");
assert!(flow.doc().is_none());
}
#[test]
fn inner_doc_comment_attaches_to_flow_body_block() {
let src = "flow greet() {\n//! Describes this flow from within.\nHi!\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl");
let body = flow.body().expect("body block");
let first_child = body
.syntax()
.children()
.next()
.expect("block has a child node");
assert_eq!(first_child.kind(), SyntaxKind::DOC_COMMENT);
let doc = body.doc().expect("inner doc accessor");
assert!(doc.is_inner());
assert_eq!(doc.lines().len(), 1);
}
#[test]
fn inner_doc_comment_attaches_to_source_file() {
let src = "//! File-level doc.\nflow greet() {\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let file = ast::SourceFile::cast(p.syntax()).expect("source file");
let doc = file.doc().expect("file-level inner doc");
assert!(doc.is_inner());
assert_eq!(doc.lines()[0].0, "File-level doc.");
}
#[test]
fn inner_doc_tolerates_leading_blank_lines() {
let src = "flow greet() {\n\n//! still attaches\nHi!\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl");
let body = flow.body().expect("body block");
let doc = body
.doc()
.expect("inner doc still attaches past blank lines");
assert!(doc.is_inner());
}
#[test]
fn inner_doc_after_real_content_does_not_attach_to_block() {
let src = "flow greet() {\nHi!\n//! not attached\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let flow: ast::FlowDecl = find_child(&p.syntax()).expect("flow decl");
let body = flow.body().expect("body block");
assert!(body.doc().is_none());
assert!(!has_node_kind(body.syntax(), SyntaxKind::DOC_COMMENT));
let leaked = body
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::TEXT)
.any(|n| n.text().to_string().contains("not attached"));
assert!(!leaked, "doc-comment token folded into visible TEXT");
}
#[test]
fn outer_and_inner_doc_tokens_are_not_trivia_in_the_tree() {
let src = "/// doc\nflow greet() {\n}\n";
let p = assert_lossless(src);
let doc_node = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::DOC_COMMENT)
.expect("DOC_COMMENT node exists");
let tok = doc_node
.first_token()
.expect("doc comment node has a token");
assert_eq!(tok.kind(), SyntaxKind::DOC_COMMENT_OUTER);
assert!(!tok.kind().is_trivia());
}