mod attr_order;
mod case_over_if;
mod deep_case_nesting;
mod element_bif;
mod newline_after_arrow;
mod strict_generator;
use crate::{BranchContext, Context, Finding, Span};
#[derive(Debug)]
pub struct Rule {
pub name: &'static str,
pub text: &'static str,
pub check: fn(&Context, &BranchContext) -> Vec<Finding>,
}
impl Rule {
pub const fn new(
name: &'static str,
text: &'static str,
check: fn(&Context, &BranchContext) -> Vec<Finding>,
) -> Self {
Self { name, text, check }
}
pub fn summary(&self) -> &str {
self.text
.lines()
.skip(1)
.find(|line| !line.trim().is_empty())
.unwrap_or(self.name)
}
}
pub const RULES: &[Rule] = &[
attr_order::RULE,
case_over_if::RULE,
deep_case_nesting::RULE,
element_bif::RULE,
newline_after_arrow::RULE,
strict_generator::RULE,
];
pub(crate) fn token_span_in_node(
branch: &BranchContext,
node: erl_parse::NodeView<'_>,
predicate: impl Fn(erl_tokenize::Token) -> bool,
) -> Option<Span> {
let index = node
.indexed_tokens()
.find_map(|(i, token)| predicate(token).then_some(i))?;
token_span(branch, index)
}
pub(crate) fn token_span(branch: &BranchContext, index: erl_parse::TokenIndex) -> Option<Span> {
branch.span_of_range(erl_parse::TokenRange::single(index))
}
#[cfg(test)]
pub(crate) mod test_support {
use super::*;
pub(crate) fn findings(
check: fn(&Context, &BranchContext) -> Vec<Finding>,
src: &str,
) -> Vec<String> {
let ctx = Context::analyze("t.erl", src.to_string()).expect("test source must scan");
assert!(
ctx.branches[0].tree.diagnostics().is_empty(),
"parse diagnostics: {:?}",
ctx.branches[0].tree.diagnostics()
);
check(&ctx, &ctx.branches[0])
.into_iter()
.map(|f| f.span.text(&ctx.text).to_string())
.collect()
}
pub(crate) fn finding_line_pairs(
check: fn(&Context, &BranchContext) -> Vec<Finding>,
src: &str,
) -> Vec<(String, String)> {
let ctx = Context::analyze("t.erl", src.to_string()).expect("test source must scan");
assert!(
ctx.branches[0].tree.diagnostics().is_empty(),
"parse diagnostics: {:?}",
ctx.branches[0].tree.diagnostics()
);
check(&ctx, &ctx.branches[0])
.into_iter()
.map(|f| {
let text = f.span.text(&ctx.text);
let first = text.lines().next().unwrap_or("").trim().to_string();
let last = text.lines().last().unwrap_or("").trim().to_string();
(first, last)
})
.collect()
}
}