Skip to main content

jsslint_core/rules/
code_width.rs

1//! Code-width rule — mirrors `journals/jss/rules/code_width.py`
2//! (JSS-WIDTH-001).
3
4use super::tex_common::{lineno_col, make_violation};
5use crate::report::Violation;
6use crate::tex::node::Node;
7use crate::tex::prose::{walk, CODE_DISPLAY_ENVS};
8use crate::tex::{position::LineIndex, ParsedTex};
9
10/// JSS-WIDTH-001 — code lines inside code-display environments
11/// (Sinput/Soutput/CodeInput/CodeOutput/verbatim/Verbatim/Code/Scode)
12/// must fit within `code_width` columns. `column` on the returned
13/// violation is deliberately the offending *length*, not a real
14/// column — matches `code_width.py::_violation`'s documented quirk
15/// (width violations are line-anchored, not position-anchored).
16pub fn check_width_001(file: &str, parsed: &ParsedTex, code_width: u32) -> Vec<Violation> {
17    let line_index = LineIndex::with_offset(&parsed.chars, parsed.line_offset);
18    let mut out = Vec::new();
19    walk(&parsed.nodes, &mut |node, _ancestors| {
20        let Node::Environment(env) = node else { return };
21        if !CODE_DISPLAY_ENVS.contains(&env.environmentname.as_str()) {
22            return;
23        }
24        let (Some(start_pos), Some(end_pos)) = content_span(&env.nodelist) else {
25            return;
26        };
27        let content: String = parsed.chars[start_pos..end_pos].iter().collect();
28        let (start_line, _) = lineno_col(&line_index, start_pos);
29        for (offset, raw_line) in content.split('\n').enumerate() {
30            let line_text = raw_line.strip_suffix('\r').unwrap_or(raw_line);
31            let width = line_text.trim_end().chars().count() as u32;
32            if width <= code_width {
33                continue;
34            }
35            out.push(make_violation(
36                file,
37                start_line + offset as u32,
38                Some(width),
39                "JSS-WIDTH-001",
40                Some(format!(
41                    "Wrap or reflow the code line to fit in {code_width} columns."
42                )),
43                None,
44            ));
45        }
46    });
47    out
48}
49
50/// `[start, end)` codepoint span between the environment's first and
51/// last child. Mirrors `code_width.py::_env_content_span`.
52fn content_span(nodelist: &[Node]) -> (Option<usize>, Option<usize>) {
53    let (Some(first), Some(last)) = (nodelist.first(), nodelist.last()) else {
54        return (None, None);
55    };
56    (Some(first.span().pos), Some(last.span().end()))
57}