rumdl 0.1.76

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD019NoMultipleSpaceAtx;
use rumdl_lib::utils::fix_utils::apply_warning_fixes;

#[test]
fn test_valid_atx_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "# Heading 1\n## Heading 2\n### Heading 3";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_invalid_atx_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Heading 1\n##   Heading 2\n###    Heading 3";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 3);
    assert_eq!(result[0].line, 1);
    assert_eq!(result[0].column, 2);
    assert_eq!(result[0].message, "Multiple spaces (2) after # in heading");
}

#[test]
fn test_mixed_atx_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "# Heading 1\n##  Heading 2\n### Heading 3\n####   Heading 4";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 2);
}

#[test]
fn test_code_block() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "```markdown\n#  Not a heading\n##   Also not a heading\n```\n# Real Heading";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_fix_atx_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Heading 1\n##   Heading 2\n###    Heading 3";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "# Heading 1\n## Heading 2\n### Heading 3");
}

#[test]
fn test_fix_mixed_atx_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "# Heading 1\n##  Heading 2\n### Heading 3\n####   Heading 4";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "# Heading 1\n## Heading 2\n### Heading 3\n#### Heading 4");
}

#[test]
fn test_preserve_code_blocks() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "# Real Heading\n```\n#  Not a heading\n```\n# Another Heading";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "# Real Heading\n```\n#  Not a heading\n```\n# Another Heading");
}

#[test]
fn test_heading_with_multiple_hashes() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "######  Heading 6";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].message, "Multiple spaces (2) after ###### in heading");
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "###### Heading 6");
}

#[test]
fn test_not_a_heading() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "This is #  not a heading\nAnd this is also #   not a heading";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_closed_atx_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Heading 1 #\n##   Heading 2 ##\n###    Heading 3 ###";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 3);
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "# Heading 1 #\n## Heading 2 ##\n### Heading 3 ###");
}

#[test]
fn test_many_spaces() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#     Heading with many spaces\n##      Another heading";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 2);
    assert_eq!(result[0].message, "Multiple spaces (5) after # in heading");
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "# Heading with many spaces\n## Another heading");
}

#[test]
fn test_empty_headings() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#\n##\n###";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_invalid_multiple_spaces() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Multiple Spaces\n\nRegular content\n\n##   More Spaces";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 2);
    assert_eq!(result[0].line, 1);
    assert_eq!(result[1].line, 5);
}

#[test]
fn test_valid_single_space() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "# Single Space\n\n## Also correct";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let result = rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_fix_multiple_spaces() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Multiple Spaces\n\n##   More Spaces";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let fixed = rule.fix(&ctx).unwrap();
    assert_eq!(fixed, "# Multiple Spaces\n\n## More Spaces");
}

#[test]
fn test_roundtrip_check_fixes_match_fix() {
    let rule = MD019NoMultipleSpaceAtx::new();

    let cases = vec![
        "#  Heading 1\n##   Heading 2\n###    Heading 3",
        "# Heading 1\n##  Heading 2\n### Heading 3\n####   Heading 4",
        "#  Multiple Spaces\n\nRegular content\n\n##   More Spaces",
        "######  Heading 6",
        "#     Heading with many spaces\n##      Another heading",
        "#  Heading 1 #\n##   Heading 2 ##\n###    Heading 3 ###",
        "# Real Heading\n```\n#  Not a heading\n```\n# Another Heading",
        "#  Heading\n\nSome text\n\n##   Another\n\nMore text\n",
    ];

    for content in cases {
        let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
        let warnings = rule.check(&ctx).unwrap();
        let fix_result = rule.fix(&ctx).unwrap();
        let check_based_fix = apply_warning_fixes(content, &warnings).unwrap();
        assert_eq!(fix_result, check_based_fix, "Mismatch for content: {content:?}");
    }
}

#[test]
fn test_roundtrip_fix_is_idempotent() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Heading 1\n##   Heading 2\n###    Heading 3";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let fixed_once = rule.fix(&ctx).unwrap();
    let ctx2 = LintContext::new(&fixed_once, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let fixed_twice = rule.fix(&ctx2).unwrap();
    assert_eq!(fixed_once, fixed_twice);
    let warnings = rule.check(&ctx2).unwrap();
    assert!(warnings.is_empty(), "Fixed content should produce no warnings");
}

#[test]
fn test_roundtrip_fix_with_trailing_newline() {
    let rule = MD019NoMultipleSpaceAtx::new();
    let content = "#  Heading\n\nText\n";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
    let warnings = rule.check(&ctx).unwrap();
    let fix_result = rule.fix(&ctx).unwrap();
    let check_based_fix = apply_warning_fixes(content, &warnings).unwrap();
    assert_eq!(fix_result, check_based_fix);
    assert!(fix_result.ends_with('\n'), "Should preserve trailing newline");
}