nu-lint 1.3.0

Linter for Nu shell scripts that helpfully suggests improvements
Documentation
use std::sync::OnceLock;

use regex::Regex;

use crate::{
    LintLevel,
    context::LintContext,
    rule::{DetectFix, Rule},
    violation::{Detection, Fix, Replacement},
};
fn trailing_space_pattern() -> &'static Regex {
    static PATTERN: OnceLock<Regex> = OnceLock::new();
    PATTERN.get_or_init(|| Regex::new(r"[ \t]+$").unwrap())
}

struct TrailingSpaceFixData {
    span: nu_protocol::Span,
}

fn check(context: &LintContext) -> Vec<(Detection, TrailingSpaceFixData)> {
    let mut violations = Vec::new();
    let source = unsafe { context.source() };
    let file_offset = context.file_offset();
    let mut byte_offset = 0;

    for (line_num, line) in source.lines().enumerate() {
        if let Some(m) = trailing_space_pattern().find(line) {
            let file_start = byte_offset + m.start();
            let file_end = byte_offset + m.end();
            let violation_span =
                nu_protocol::Span::new(file_start + file_offset, file_end + file_offset);

            violations.push((
                Detection::from_global_span(
                    format!("Line {} has trailing whitespace", line_num + 1),
                    violation_span,
                )
                .with_primary_label("trailing whitespace"),
                TrailingSpaceFixData {
                    span: violation_span,
                },
            ));
        }
        // Update byte offset for next line (including newline character)
        byte_offset += line.len() + 1;
    }
    violations
}
struct NoTrailingSpaces;

impl DetectFix for NoTrailingSpaces {
    type FixInput<'a> = TrailingSpaceFixData;

    fn id(&self) -> &'static str {
        "no_trailing_spaces"
    }

    fn short_description(&self) -> &'static str {
        "Eliminate trailing spaces at the end of lines"
    }

    fn source_link(&self) -> Option<&'static str> {
        Some("https://www.nushell.sh/book/style_guide.html#multi-line-format")
    }

    fn level(&self) -> LintLevel {
        LintLevel::Off
    }

    fn detect<'a>(&self, context: &'a LintContext) -> Vec<(Detection, Self::FixInput<'a>)> {
        check(context)
    }

    fn fix(&self, _context: &LintContext, fix_data: &Self::FixInput<'_>) -> Option<Fix> {
        Some(Fix {
            explanation: "Remove trailing whitespace".into(),
            replacements: vec![Replacement::new(fix_data.span, String::new())],
        })
    }
}

pub static RULE: &dyn Rule = &NoTrailingSpaces;
#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod generated_fix;
#[cfg(test)]
mod ignore_good;