use crate::analysis::findings::Finding;
use crate::docs::{Check, LONG_LINE_MAX, Line, finding};
pub fn check(line: &Line<'_>, chars: &[char], file_path: &str, out: &mut Vec<Finding>) {
trailing_whitespace(line, chars, file_path, out);
if !line.in_fence {
tab_character(line, chars, file_path, out);
heading(line, chars, file_path, out);
long_line(line, chars, file_path, out);
}
}
fn trailing_whitespace(line: &Line<'_>, chars: &[char], file_path: &str, out: &mut Vec<Finding>) {
let kept = chars
.iter()
.rposition(|c| *c != ' ' && *c != '\t')
.map_or(0, |last| last + 1);
if kept == chars.len() {
return;
}
out.push(finding(
Check::TrailingWhitespace,
file_path,
line.number,
kept as u32 + 1,
format!("{} trailing whitespace character(s)", chars.len() - kept),
));
}
fn tab_character(line: &Line<'_>, chars: &[char], file_path: &str, out: &mut Vec<Finding>) {
let Some(index) = chars.iter().position(|c| *c == '\t') else {
return;
};
out.push(finding(
Check::TabCharacter,
file_path,
line.number,
index as u32 + 1,
"tab character".to_owned(),
));
}
const MAX_HEADING_LEVEL: usize = 6;
fn heading(line: &Line<'_>, chars: &[char], file_path: &str, out: &mut Vec<Finding>) {
if !line.text.starts_with('#') {
return;
}
let level = chars.iter().take_while(|c| **c == '#').count();
if level > MAX_HEADING_LEVEL {
return;
}
let rest = &chars[level..];
if rest.iter().all(|c| c.is_whitespace()) {
out.push(finding(
Check::EmptyHeading,
file_path,
line.number,
1,
format!("level-{level} heading has no text"),
));
} else if !rest[0].is_whitespace() {
out.push(finding(
Check::MissingSpaceAfterHeading,
file_path,
line.number,
level as u32 + 1,
format!("no space after the {level} heading marker(s)"),
));
}
}
fn long_line(line: &Line<'_>, chars: &[char], file_path: &str, out: &mut Vec<Finding>) {
let length = chars.len();
if length <= LONG_LINE_MAX {
return;
}
out.push(finding(
Check::LongLine,
file_path,
line.number,
LONG_LINE_MAX as u32 + 1,
format!("line is {length} characters (limit {LONG_LINE_MAX})"),
));
}