rumdl 0.0.12

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
use crate::rule::{Fix, LintError, LintResult, LintWarning, Rule};
use regex::Regex;

#[derive(Debug, Default)]
pub struct MD037NoSpaceInEmphasis;

impl MD037NoSpaceInEmphasis {
    fn find_emphasis_issues(content: &str) -> Vec<(usize, usize, String)> {
        let mut issues = Vec::new();
        let re = Regex::new(r"(\*\s|\s\*|\*{2}\s|\s\*{2}|_\s|\s_|_{2}\s|\s_{2})([^*_]+?)(\*\s|\s\*|\*{2}\s|\s\*{2}|_\s|\s_|_{2}\s|\s_{2})").unwrap();
        
        for line in content.lines() {
            for cap in re.captures_iter(line) {
                let start = cap.get(0).unwrap().start();
                let end = cap.get(0).unwrap().end();
                let text = cap[2].trim().to_string();
                let is_double = cap[1].len() > 2;
                let fixed = if is_double {
                    format!("**{}**", text)
                } else {
                    format!("*{}*", text)
                };
                issues.push((start, end, fixed));
            }
        }
        issues
    }
}

impl Rule for MD037NoSpaceInEmphasis {
    fn name(&self) -> &'static str {
        "MD037"
    }

    fn description(&self) -> &'static str {
        "Spaces inside emphasis markers should be removed"
    }

    fn check(&self, content: &str) -> LintResult {
        let mut warnings = Vec::new();

        for (line_num, line) in content.lines().enumerate() {
            let issues = Self::find_emphasis_issues(line);
            for (start, _, fixed) in issues {
                warnings.push(LintWarning {
                    line: line_num + 1,
                    column: start + 1,
                    message: "Spaces inside emphasis markers should be removed".to_string(),
                    fix: Some(Fix {
                        line: line_num + 1,
                        column: start + 1,
                        replacement: fixed,
                    }),
                });
            }
        }

        Ok(warnings)
    }

    fn fix(&self, content: &str) -> Result<String, LintError> {
        let mut result = String::new();
        
        for line in content.lines() {
            let mut fixed_line = line.to_string();
            let issues = Self::find_emphasis_issues(line);
            
            for (start, end, fixed) in issues.iter().rev() {
                fixed_line.replace_range(*start..*end, fixed);
            }
            
            result.push_str(&fixed_line);
            result.push('\n');
        }

        if !content.ends_with('\n') {
            result.pop();
        }

        Ok(result)
    }
}