use super::{Rule, token_span_in_node};
use crate::{BranchContext, Context, Finding};
pub const RULE: Rule = Rule::new(
"strict_generator",
include_str!("../../rules/strict_generator/rule.md"),
check,
);
fn check(_ctx: &Context, branch: &BranchContext) -> Vec<Finding> {
let mut errors = Vec::new();
for root in branch.tree.roots() {
walk(branch, root, &mut errors);
}
errors
}
fn walk(branch: &BranchContext, node: erl_parse::NodeView<'_>, errors: &mut Vec<Finding>) {
if node.kind() == erl_parse::SyntaxKind::ZipQualifier {
return;
}
check_node(branch, node, errors);
for child in node.children() {
walk(branch, child, errors);
}
}
fn check_node(branch: &BranchContext, node: erl_parse::NodeView<'_>, errors: &mut Vec<Finding>) {
let expected = match node.kind() {
erl_parse::SyntaxKind::Generator | erl_parse::SyntaxKind::MapGenerator => {
erl_tokenize::Symbol::LeftArrow
}
erl_parse::SyntaxKind::BitstringGenerator => erl_tokenize::Symbol::DoubleLeftArrow,
_ => return,
};
let Some(span) = token_span_in_node(branch, node, |token| {
token.kind() == erl_tokenize::TokenKind::Symbol(expected)
}) else {
return;
};
errors.push(Finding {
span,
node: node.node_id(),
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::test_support::findings;
#[test]
fn flags_list_generator() {
let src = "-module(t).\nfoo() -> [X || X <- [1, 2, 3]].\n";
assert_eq!(findings(check, src), ["<-"]);
}
#[test]
fn flags_bitstring_generator() {
let src = "-module(t).\nfoo() -> [X || <<X:8>> <= <<1, 2, 3>>].\n";
assert_eq!(findings(check, src), ["<="]);
}
#[test]
fn flags_map_generator() {
let src = "-module(t).\nfoo(M) -> #{K => V || K := V <- M}.\n";
assert_eq!(findings(check, src), ["<-"]);
}
#[test]
fn ignores_strict_list_generator() {
let src = "-module(t).\nfoo() -> [X || X <:- [1, 2, 3]].\n";
assert!(findings(check, src).is_empty());
}
#[test]
fn ignores_strict_bitstring_generator() {
let src = "-module(t).\nfoo() -> [X || <<X:8>> <:= <<1, 2, 3>>].\n";
assert!(findings(check, src).is_empty());
}
#[test]
fn ignores_strict_map_generator() {
let src = "-module(t).\nfoo(M) -> #{K => V || K := V <:- M}.\n";
assert!(findings(check, src).is_empty());
}
#[test]
fn ignores_zip_generator() {
let src = "-module(t).\nfoo() -> [{X, Y} || X <- [1, 2] && Y <- [3, 4]].\n";
assert!(findings(check, src).is_empty());
}
#[test]
fn flags_each_generator_in_a_comprehension_once() {
let src = "-module(t).\nfoo() -> [[X || X <- L] || L <- [[1], [2]]].\n";
assert_eq!(findings(check, src), ["<-", "<-"]);
}
#[test]
fn ng_fixture_has_three_findings() {
let src = include_str!("../../rules/strict_generator/ng.erl");
assert_eq!(findings(check, src).len(), 3);
}
#[test]
fn ok_fixture_has_no_findings() {
let src = include_str!("../../rules/strict_generator/ok.erl");
assert!(findings(check, src).is_empty());
}
}