use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD009TrailingSpaces;
#[test]
fn test_md009_valid() {
let rule = MD009TrailingSpaces::default();
let content = "Line without trailing spaces\nAnother line without trailing spaces\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_md009_invalid() {
let rule = MD009TrailingSpaces::default();
let content = "Line with trailing spaces \nAnother line with trailing spaces \n";
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].line, 2);
assert_eq!(result[0].message, "3 trailing spaces found");
}
#[test]
fn test_md009_empty_lines() {
let rule = MD009TrailingSpaces::default();
let content = "Line without trailing spaces\n \nAnother line without trailing spaces\n";
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].line, 2);
assert_eq!(result[0].message, "Empty line has trailing spaces");
}
#[test]
fn test_md009_code_blocks() {
let rule = MD009TrailingSpaces::default();
let content = "Normal line\n```\nCode with spaces \nMore code \n```\nNormal line \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0); }
#[test]
fn test_md009_strict_mode() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "Line with two spaces \nCode block```\nWith spaces \n```\n";
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_md009_line_breaks() {
let rule = MD009TrailingSpaces::default();
let content = "This is a line \nWith hard breaks \nBut this has three \n";
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].line, 3);
}
#[test]
fn test_md009_custom_br_spaces() {
let rule = MD009TrailingSpaces::new(3, false);
let content = "Line with two spaces \nLine with three \n";
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].line, 1);
}
#[test]
fn test_md009_fix() {
let rule = MD009TrailingSpaces::default();
let content = "Line with spaces \nAnother line \nNo spaces\n \n```\nCode \n```\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(
result,
"Line with spaces\nAnother line \nNo spaces\n\n```\nCode \n```\n"
);
}
#[test]
fn test_md009_fix_strict() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "Line with spaces \nAnother line \nNo spaces\n \n```\nCode \n```\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(result, "Line with spaces\nAnother line\nNo spaces\n\n```\nCode\n```\n");
}
#[test]
fn test_md009_trailing_tabs() {
let rule = MD009TrailingSpaces::default();
let content = "Line with trailing tab\t\nLine with tabs and spaces\t \nMixed at end \t\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0); }
#[test]
fn test_md009_multiple_trailing_spaces() {
let rule = MD009TrailingSpaces::default();
let content = "One space \nTwo spaces \nThree spaces \nFour spaces \nFive spaces \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 4); assert_eq!(result[0].line, 1);
assert_eq!(result[0].message, "Trailing space found");
assert_eq!(result[1].line, 3);
assert_eq!(result[1].message, "3 trailing spaces found");
assert_eq!(result[2].line, 4);
assert_eq!(result[2].message, "4 trailing spaces found");
assert_eq!(result[3].line, 5);
assert_eq!(result[3].message, "5 trailing spaces found");
}
#[test]
fn test_md009_lists_with_trailing_spaces() {
let rule = MD009TrailingSpaces::default();
let content = "- List item without spaces\n- List item with spaces \n - Nested with spaces \n - Nested without\n* Another list \n";
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].line, 3);
assert_eq!(result[0].message, "3 trailing spaces found");
}
#[test]
fn test_md009_blockquote_empty_lines() {
let rule = MD009TrailingSpaces::default();
let content = "> Quote\n> \n> More quote\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0); }
#[test]
fn test_md009_blockquote_truly_empty() {
let rule = MD009TrailingSpaces::default();
let content = "> Quote\n> \n> More quote\n";
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].line, 2);
assert_eq!(result[0].message, "3 trailing spaces found");
}
#[test]
fn test_md009_fix_preserves_line_breaks() {
let rule = MD009TrailingSpaces::new(2, false);
let content = "Line with one space \nLine with two \nLine with three \nLine with four \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(
result,
"Line with one space\nLine with two \nLine with three\nLine with four\n"
);
}
#[test]
fn test_md009_fix_empty_lines() {
let rule = MD009TrailingSpaces::default();
let content = "Text\n \nMore text\n \nEnd\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(result, "Text\n\nMore text\n\nEnd\n");
}
#[test]
fn test_md009_br_spaces_configuration() {
let rule = MD009TrailingSpaces::new(4, false);
let content = "Two spaces \nThree spaces \nFour spaces \nFive spaces \n";
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[1].line, 2);
assert_eq!(result[2].line, 4);
}
#[test]
fn test_md009_last_line_handling() {
let rule = MD009TrailingSpaces::default();
let content_with_newline = "Line one \nLine two \nLast line \n";
let ctx = LintContext::new(content_with_newline, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content_without_newline = "Line one \nLine two \nLast line ";
let ctx = LintContext::new(
content_without_newline,
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1); assert_eq!(result[0].line, 3);
}
#[test]
fn test_md009_fix_last_line() {
let rule = MD009TrailingSpaces::default();
let content = "Line one \nLine two \nLast line ";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(result, "Line one \nLine two \nLast line");
let content = "Line one \nLine two \nLast line \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(result, "Line one \nLine two \nLast line \n");
}
#[test]
fn test_md009_code_blocks_strict_mode() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "```python\ndef hello(): \n print('world') \n```\n";
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_md009_fix_blockquote_empty_lines() {
let rule = MD009TrailingSpaces::default();
let content = "> Quote\n> \n> More quote\n>\n> End\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(result, "> Quote\n>\n> More quote\n>\n> End\n"); }
#[test]
fn test_md009_mixed_content() {
let rule = MD009TrailingSpaces::default();
let content = "# Heading \n\nParagraph with line break \nAnother line \n\n- List item \n- Another item \n\n```\ncode \n```\n\n> Quote \n> \n> More \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_md009_column_positions() {
let rule = MD009TrailingSpaces::default();
let content = "Short \nA longer line with spaces \n";
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].line, 2);
assert_eq!(result[0].column, 26); assert_eq!(result[0].end_column, 29); }
#[test]
fn test_md009_only_spaces_line() {
let rule = MD009TrailingSpaces::default();
let content = "Text\n \nMore text\n";
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].line, 2);
assert_eq!(result[0].message, "Empty line has trailing spaces");
}
#[test]
fn test_md009_heading_with_trailing_spaces() {
let rule = MD009TrailingSpaces::default();
let content = "# Heading \n## Another heading \n### Third \n";
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].line, 2);
assert_eq!(result[0].message, "3 trailing spaces found");
}
#[test]
fn test_md009_table_with_trailing_spaces() {
let rule = MD009TrailingSpaces::default();
let content = "| Column 1 | Column 2 |\n|----------|-----------| \n| Data | More data | \n";
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].line, 3);
}
#[test]
fn test_md009_fix_with_crlf() {
let rule = MD009TrailingSpaces::default();
let content = "Line one \r\nLine two \r\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(result, "Line one \r\nLine two\r\n");
}
#[test]
fn test_md009_indented_code_non_strict() {
let rule = MD009TrailingSpaces::new(2, false);
let content = "Text\n\n indented code \n more code \n\nText\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_md009_fix_complex_document() {
let rule = MD009TrailingSpaces::default();
let content =
"# Title \n\nParagraph \n\n- List \n - Nested \n\n```\ncode \n```\n\n> Quote \n> \n\nEnd ";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx).unwrap();
assert_eq!(
result,
"# Title\n\nParagraph \n\n- List\n - Nested \n\n```\ncode \n```\n\n> Quote\n>\n\nEnd" );
}
#[test]
fn test_md009_unicode_content() {
let rule = MD009TrailingSpaces::default();
let content = "Unicode text 你好 \nAnother line 世界 \n";
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].line, 2);
assert_eq!(result[0].message, "3 trailing spaces found");
}
#[test]
fn test_md009_nested_blockquotes() {
let rule = MD009TrailingSpaces::default();
let content = "> Level 1 \n> > Level 2 \n> > > Level 3 \n";
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].line, 2);
}
#[test]
fn test_md009_euro_sign_fix_range() {
let rule = MD009TrailingSpaces::new(2, true); let content = "- 1€ expenses \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].line, 1);
let fix = warnings[0].fix.as_ref().expect("Should have fix");
assert_eq!(fix.range.start, 15);
assert_eq!(fix.range.end, 16);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- 1€ expenses\n");
}
#[test]
fn test_md009_multiple_euro_signs_fix_range() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "€100 + €50 = €150 \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].line, 1);
assert_eq!(warnings[0].message, "3 trailing spaces found");
let fix = warnings[0].fix.as_ref().expect("Should have fix");
assert_eq!(fix.range.start, 23);
assert_eq!(fix.range.end, 26);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "€100 + €50 = €150\n");
}
#[test]
fn test_md009_cjk_characters_fix_range() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "Hello 你好世界 \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
let fix = warnings[0].fix.as_ref().expect("Should have fix");
assert_eq!(fix.range.start, 18);
assert_eq!(fix.range.end, 21);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "Hello 你好世界\n");
}
#[test]
fn test_md009_emoji_fix_range() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "Party 🎉🎉🎉 \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
let fix = warnings[0].fix.as_ref().expect("Should have fix");
assert_eq!(fix.range.start, 18);
assert_eq!(fix.range.end, 21);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "Party 🎉🎉🎉\n");
}
#[test]
fn test_md009_mixed_multibyte_fix_range() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "Price: €100 你好 🎉 \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].message, "2 trailing spaces found");
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "Price: €100 你好 🎉\n");
}
#[test]
fn test_md009_issue_248_exact_repro() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "- foobar \n- 1€ expenses \n\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
for warning in &warnings {
let fix = warning.fix.as_ref().expect("Should have fix");
assert!(fix.range.start < fix.range.end, "Fix range should not be empty");
}
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- foobar\n- 1€ expenses\n\n");
}
#[test]
fn test_md009_warning_based_fix_multibyte() {
use rumdl_lib::utils::fix_utils::apply_warning_fixes;
let rule = MD009TrailingSpaces::new(2, true);
let content = "- 1€ expenses \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
let fixed = apply_warning_fixes(content, &warnings).expect("Should apply fixes");
assert_eq!(fixed, "- 1€ expenses\n");
}
#[test]
fn test_md009_multiple_lines_multibyte_fix() {
use rumdl_lib::utils::fix_utils::apply_warning_fixes;
let rule = MD009TrailingSpaces::new(2, true);
let content = "€ price \n¥ yen \n£ pound \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 3);
let fixed = apply_warning_fixes(content, &warnings).expect("Should apply fixes");
assert_eq!(fixed, "€ price\n¥ yen\n£ pound\n");
}
#[test]
fn test_md009_column_positions_multibyte() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "€€€ \n"; let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].column, 4);
assert_eq!(warnings[0].end_column, 7);
}
#[test]
fn test_md009_korean_fix_range() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "안녕하세요 \n"; let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
let fix = warnings[0].fix.as_ref().expect("Should have fix");
assert_eq!(fix.range.start, 15);
assert_eq!(fix.range.end, 18);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "안녕하세요\n");
}
#[test]
fn test_md009_combining_characters_fix() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "café \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "café\n");
}
#[test]
fn test_md009_list_with_multibyte_marker_content() {
let rule = MD009TrailingSpaces::new(2, true);
let content = "- 价格: €50 \n- 價格: ¥100 \n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- 价格: €50\n- 價格: ¥100\n");
}