use super::Rule;
use crate::{BranchContext, Context, Finding};
pub const RULE: Rule = Rule::new(
"deep_case_nesting",
include_str!("../../rules/deep_case_nesting/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, 0, &mut errors);
}
errors
}
fn walk(
branch: &BranchContext,
node: erl_parse::NodeView<'_>,
case_depth: usize,
errors: &mut Vec<Finding>,
) {
if is_depth_break(node.kind()) {
for child in node.children() {
walk(branch, child, 0, errors);
}
return;
}
let child_depth = match node.kind() {
erl_parse::SyntaxKind::CaseExpr => {
if case_depth + 1 >= 3
&& let Some(span) = branch.span_of_range(node.token_range())
{
errors.push(Finding {
span,
node: node.node_id(),
});
}
case_depth + 1
}
_ => case_depth,
};
for child in node.children() {
walk(branch, child, child_depth, errors);
}
}
fn is_depth_break(kind: erl_parse::SyntaxKind) -> bool {
matches!(
kind,
erl_parse::SyntaxKind::AnonymousFun
| erl_parse::SyntaxKind::NamedFun
| erl_parse::SyntaxKind::TryExpr
| erl_parse::SyntaxKind::ReceiveExpr
| erl_parse::SyntaxKind::BeginExpr
| erl_parse::SyntaxKind::MaybeExpr
| erl_parse::SyntaxKind::ListExpr
| erl_parse::SyntaxKind::TupleExpr
| erl_parse::SyntaxKind::MapExpr
| erl_parse::SyntaxKind::BitstringExpr
| erl_parse::SyntaxKind::ListComprehension
| erl_parse::SyntaxKind::MapComprehension
| erl_parse::SyntaxKind::BinaryComprehension
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::test_support::{finding_line_pairs, findings};
#[test]
fn flags_case_three_levels_deep() {
let src = "\
-module(t).
foo(A, B, C) ->
case A of
a ->
case B of
b ->
case C of
c ->
ok
end
end
end.
";
assert_eq!(
finding_line_pairs(check, src),
[("case C of".to_string(), "end".to_string())]
);
}
#[test]
fn flags_every_case_three_or_more_levels_deep() {
let src = "\
-module(t).
foo(A, B, C, D, E) ->
case A of
a ->
case B of
b ->
case C of
c ->
case D of
d ->
case E of
e ->
ok
end
end
end
end
end.
";
assert_eq!(
finding_line_pairs(check, src),
[
("case C of".to_string(), "end".to_string()),
("case D of".to_string(), "end".to_string()),
("case E of".to_string(), "end".to_string()),
]
);
}
#[test]
fn ignores_case_two_levels_deep() {
let src = "\
-module(t).
foo(A, B) ->
case A of
a ->
case B of
b ->
ok
end
end.
";
assert!(findings(check, src).is_empty());
}
#[test]
fn resets_depth_at_fun_boundary() {
let src = "\
-module(t).
foo(A) ->
case A of
a ->
fun() ->
case 1 of
1 ->
case 2 of
2 ->
ok
end
end
end()
end.
";
assert!(findings(check, src).is_empty());
}
#[test]
fn resets_depth_at_block_expressions() {
let src = "\
-module(t).
foo(A, B, C) ->
case A of
a ->
try
case B of
b ->
case C of
c ->
ok
end
end
catch
_:_ ->
error
end
end.
";
assert!(findings(check, src).is_empty());
}
#[test]
fn resets_depth_at_container_literals() {
let src = "\
-module(t).
foo(A, B, C) ->
case A of
a ->
{case B of
b ->
case C of
c ->
ok
end
end}
end.
";
assert!(findings(check, src).is_empty());
}
#[test]
fn ng_fixture_has_four_findings() {
let src = include_str!("../../rules/deep_case_nesting/ng.erl");
assert_eq!(findings(check, src).len(), 4);
}
#[test]
fn ok_fixture_has_no_findings() {
let src = include_str!("../../rules/deep_case_nesting/ok.erl");
assert!(findings(check, src).is_empty());
}
}