use super::{Rule, Scope, Violation};
use crate::column::space_indent;
use crate::nodes::{is_block_scalar, starts_a_flow_collection};
use crate::yaml::SyntaxNode;
fn entry_is_empty(entry: &SyntaxNode) -> bool {
entry
.text()
.try_for_each_chunk(|chunk| {
if chunk.chars().all(|c| c.is_whitespace() || c == ',') {
Ok(())
} else {
Err(())
}
})
.is_ok()
}
pub(super) fn check_block_scalar_in_flow(
node: &SyntaxNode,
in_flow: bool,
violations: &mut Vec<Violation>,
) {
use crate::SyntaxKind;
let Some(first) = node.first_token() else {
return;
};
let is_block_header = is_block_scalar(node)
|| (first.kind() == SyntaxKind::STRING && first.text().starts_with(['|', '>']));
if is_block_header && in_flow {
violations.push(Violation::error_at(
Rule::Other,
first.text_range(),
"Block scalar cannot appear inside a flow collection",
));
}
}
pub(super) fn check_flow_collection_commas(node: &SyntaxNode, violations: &mut Vec<Violation>) {
if !starts_a_flow_collection(node) {
return;
}
let entry_kind = match node.kind() {
crate::SyntaxKind::MAPPING => crate::SyntaxKind::MAPPING_ENTRY,
crate::SyntaxKind::SEQUENCE => crate::SyntaxKind::SEQUENCE_ENTRY,
_ => return,
};
let entries: Vec<_> = node.children().filter(|n| n.kind() == entry_kind).collect();
for entry in &entries {
if entry_is_empty(entry) {
violations.push(Violation::error_at(
Rule::Other,
node.text_range(),
"Empty entry in flow collection",
));
break;
}
}
for pair in entries.windows(2) {
if !crate::nodes::has_child_token(&pair[0], |k| k == crate::SyntaxKind::COMMA) {
violations.push(Violation::error_at(
Rule::MissingSyntax,
pair[1].text_range(),
"Missing comma between flow collection entries",
));
}
}
}
pub(super) fn check_flow_pair_placement(
node: &SyntaxNode,
in_flow: bool,
violations: &mut Vec<Violation>,
) {
if starts_a_flow_collection(node) || !in_flow {
return;
}
let is_sequence_entry = node
.parent()
.filter(|p| p.kind() == crate::SyntaxKind::SEQUENCE_ENTRY)
.and_then(|p| p.parent())
.is_some_and(|s| starts_a_flow_collection(&s));
if !is_sequence_entry {
violations.push(Violation::error_at(
Rule::Other,
node.text_range(),
"Mapping without braces can only be a flow sequence entry",
));
}
}
pub(super) fn check_flow_continuation_indent(
node: &SyntaxNode,
scope: Scope,
violations: &mut Vec<Violation>,
) {
if !starts_a_flow_collection(node) {
return;
}
if scope.in_flow {
return;
}
let mut line_start = true;
for el in node.descendants_with_tokens() {
let rowan::NodeOrToken::Token(t) = el else {
continue;
};
match t.kind() {
crate::SyntaxKind::NEWLINE => line_start = true,
crate::SyntaxKind::INDENT | crate::SyntaxKind::WHITESPACE => {}
_ if line_start && matches!(t.text(), "---" | "...") => {
violations.push(Violation::error_at(
Rule::InvalidDocumentMarker,
t.text_range(),
"Document marker inside a flow collection",
));
return;
}
_ => line_start = false,
}
}
let Some(required) = scope.continuation_indent else {
return;
};
let mut indent: Option<usize> = None;
for el in node.descendants_with_tokens() {
let rowan::NodeOrToken::Token(t) = el else {
continue;
};
match t.kind() {
crate::SyntaxKind::NEWLINE => indent = Some(0),
crate::SyntaxKind::INDENT | crate::SyntaxKind::WHITESPACE if indent.is_some() => {
indent = Some(space_indent(t.text()));
}
crate::SyntaxKind::COMMENT => indent = None,
_ if indent.take().is_some_and(|i| i < required) => {
violations.push(Violation::error_at(
Rule::InvalidIndentation,
node.text_range(),
"Flow collection continuation line must be indented",
));
return;
}
_ => {}
}
}
}
pub(super) fn check_tag_followed_by_comma(
token: &rowan::SyntaxToken<crate::Lang>,
violations: &mut Vec<Violation>,
) {
let mut current = token.next_sibling_or_token();
while let Some(sibling) = current {
match &sibling {
rowan::NodeOrToken::Token(t) => {
match t.kind() {
crate::SyntaxKind::WHITESPACE | crate::SyntaxKind::NEWLINE => {
current = t.next_sibling_or_token();
continue;
}
crate::SyntaxKind::COMMA => {
violations.push(Violation::error_at(
Rule::InvalidTag,
token.text_range(),
"Invalid comma after tag",
));
return;
}
_ => {
return;
}
}
}
rowan::NodeOrToken::Node(_) => return,
}
}
}