use criterion::{Criterion, criterion_group, criterion_main};
use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::*;
use std::hint::black_box;
fn generate_problematic_content(size: usize) -> String {
let mut content = String::with_capacity(size * 100);
for i in 0..size {
if i % 10 == 0 {
content.push_str(&format!("### Heading {i} (should be H1)\n\n"));
}
if i % 15 == 0 {
content.push_str(&format!("Heading {i}\n"));
content.push_str("=============\n\n");
}
if i % 8 == 0 {
content.push_str(&format!("* Item {i}\n"));
content.push_str(&format!("- Item {}\n", i + 1));
content.push_str(&format!("+ Item {}\n\n", i + 2));
}
if i % 12 == 0 {
content.push_str(&format!("* Item {i}\n"));
content.push_str(&format!(" * Badly indented item {i}\n\n"));
}
if i % 18 == 0 {
content.push_str(&format!("* Item {i}\n"));
content.push_str(&format!(" * Wrong indent {i}\n\n"));
}
content.push_str(&format!("Line {i} with trailing spaces \n"));
if i % 25 == 0 {
content.push_str(&format!("Line {i}\twith\ttabs\n"));
}
if i % 30 == 0 {
content.push_str("\n\n\n");
}
if i % 7 == 0 {
content.push_str(&format!("This is a very long line {i} that exceeds the default line length limit of 80 characters and should be flagged by MD013 rule for being too long.\n"));
}
if i % 22 == 0 {
content.push_str(&format!("#Heading without space {i}\n\n"));
}
if i % 24 == 0 {
content.push_str(&format!("## Heading with extra spaces {i}\n\n"));
}
if i % 26 == 0 {
content.push_str(&format!("#Closed heading{i}#\n\n"));
}
if i % 28 == 0 {
content.push_str(&format!("# Closed heading {i} #\n\n"));
}
if i % 35 == 0 {
content.push_str(&format!("Text before heading\n# Heading {i}\nText after heading\n\n"));
}
if i % 40 == 0 {
content.push_str(&format!(" # Indented heading {i}\n\n"));
}
if i % 16 == 0 {
content.push_str(&format!("# Heading with punctuation {i}!\n\n"));
}
if i % 45 == 0 {
content.push_str(&format!("> Blockquote with extra spaces {i}\n\n"));
}
if i % 14 == 0 {
content.push_str(&format!("- Item with extra space {i}\n\n"));
}
if i % 50 == 0 {
content.push_str(&format!("Text before\n```\ncode {i}\n```\nText after\n\n"));
}
if i % 55 == 0 {
content.push_str(&format!("Text before\n* List item {i}\nText after\n\n"));
}
if i % 17 == 0 {
content.push_str(&format!("Text with <b>HTML tags</b> number {i}\n"));
}
if i % 19 == 0 {
content.push_str(&format!("Visit http://example{i}.com for more info\n"));
}
if i % 60 == 0 {
content.push_str("---\n\n***\n\n");
}
if i % 13 == 0 {
content.push_str(&format!("Text with * bad emphasis * number {i}\n"));
}
if i % 21 == 0 {
content.push_str(&format!("Text with ` bad code ` number {i}\n"));
}
if i % 32 == 0 {
content.push_str(&format!("[ Link text ](http://example{i}.com)\n"));
}
if i % 65 == 0 {
content.push_str("```\ncode without language\n```\n\n");
}
if i % 70 == 0 {
content.push_str(&format!("[empty link {i}]()\n"));
}
if i % 11 == 0 {
content.push_str(&format!("Text mentioning javascript and github number {i}\n"));
}
if i % 75 == 0 {
content.push_str(&format!("\n"));
}
if i % 23 == 0 {
content.push_str(&format!(
"Text with _underscore emphasis_ and *asterisk emphasis* {i}\n"
));
}
if i % 27 == 0 {
content.push_str(&format!(
"Text with __underscore strong__ and **asterisk strong** {i}\n"
));
}
if i % 80 == 0 {
content.push_str(&format!("[unused{i}]: http://example{i}.com\n"));
}
content.push_str(&format!("Regular paragraph {i} with some content.\n\n"));
}
content
}
fn bench_fix_performance(c: &mut Criterion) {
let content = generate_problematic_content(100);
let ctx = LintContext::new(&content, rumdl_lib::config::MarkdownFlavor::Standard, None);
c.bench_function("MD001 fix", |b| {
let rule = MD001HeadingIncrement::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD003 fix", |b| {
let rule = MD003HeadingStyle::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD004 fix", |b| {
let rule = MD004UnorderedListStyle::new(rumdl_lib::rules::UnorderedListStyle::Consistent);
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD005 fix", |b| {
let rule = MD005ListIndent::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD007 fix", |b| {
let rule = MD007ULIndent::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD009 fix", |b| {
let rule = MD009TrailingSpaces::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD010 fix", |b| {
let rule = MD010NoHardTabs::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD012 fix", |b| {
let rule = MD012NoMultipleBlanks::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD018 fix", |b| {
let rule = MD018NoMissingSpaceAtx::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD019 fix", |b| {
let rule = MD019NoMultipleSpaceAtx;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD020 fix", |b| {
let rule = MD020NoMissingSpaceClosedAtx;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD021 fix", |b| {
let rule = MD021NoMultipleSpaceClosedAtx;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD022 fix", |b| {
let rule = MD022BlanksAroundHeadings::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD023 fix", |b| {
let rule = MD023HeadingStartLeft;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD026 fix", |b| {
let rule = MD026NoTrailingPunctuation::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD027 fix", |b| {
let rule = MD027MultipleSpacesBlockquote::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD030 fix", |b| {
let rule = MD030ListMarkerSpace::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD031 fix", |b| {
let rule = MD031BlanksAroundFences::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD032 fix", |b| {
let rule = MD032BlanksAroundLists::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD034 fix", |b| {
let rule = MD034NoBareUrls;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD035 fix", |b| {
let rule = MD035HRStyle::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD037 fix", |b| {
let rule = MD037NoSpaceInEmphasis;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD038 fix", |b| {
let rule = MD038NoSpaceInCode::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD039 fix", |b| {
let rule = MD039NoSpaceInLinks;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD040 fix", |b| {
let rule = MD040FencedCodeLanguage::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD044 fix", |b| {
let proper_names = vec!["JavaScript".to_string(), "GitHub".to_string()];
let rule = MD044ProperNames::new(proper_names, true);
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD047 fix", |b| {
let rule = MD047SingleTrailingNewline;
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD049 fix", |b| {
let rule = MD049EmphasisStyle::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD053 fix", |b| {
let rule = MD053LinkImageReferenceDefinitions::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
}
fn bench_fix_performance_large(c: &mut Criterion) {
let content = generate_problematic_content(1000); let ctx = LintContext::new(&content, rumdl_lib::config::MarkdownFlavor::Standard, None);
c.bench_function("MD009 fix large", |b| {
let rule = MD009TrailingSpaces::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD012 fix large", |b| {
let rule = MD012NoMultipleBlanks::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD018 fix large", |b| {
let rule = MD018NoMissingSpaceAtx::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD026 fix large", |b| {
let rule = MD026NoTrailingPunctuation::default();
b.iter(|| rule.fix(black_box(&ctx)))
});
c.bench_function("MD037 fix large", |b| {
let rule = MD037NoSpaceInEmphasis;
b.iter(|| rule.fix(black_box(&ctx)))
});
}
fn bench_string_operations(c: &mut Criterion) {
let content = "Line with trailing spaces \n".repeat(1000);
c.bench_function("trim_end approach", |b| {
b.iter(|| content.lines().map(str::trim_end).collect::<Vec<_>>().join("\n"))
});
c.bench_function("regex replace approach", |b| {
use regex::Regex;
let re = Regex::new(r" +$").unwrap();
b.iter(|| re.replace_all(black_box(&content), "").to_string())
});
c.bench_function("manual char iteration", |b| {
b.iter(|| {
let mut result = String::with_capacity(content.len());
for line in content.lines() {
let trimmed = line.trim_end();
result.push_str(trimmed);
result.push('\n');
}
result
})
});
}
fn bench_memory_patterns(c: &mut Criterion) {
let content = generate_problematic_content(500);
c.bench_function("string_with_capacity", |b| {
b.iter(|| {
let mut result = String::with_capacity(content.len());
for line in content.lines() {
result.push_str(line.trim_end());
result.push('\n');
}
result
})
});
c.bench_function("string_without_capacity", |b| {
b.iter(|| {
let mut result = String::new();
for line in content.lines() {
result.push_str(line.trim_end());
result.push('\n');
}
result
})
});
c.bench_function("collect_approach", |b| {
b.iter(|| content.lines().map(str::trim_end).collect::<Vec<_>>().join("\n"))
});
}
criterion_group!(
benches,
bench_fix_performance,
bench_fix_performance_large,
bench_string_operations,
bench_memory_patterns
);
criterion_main!(benches);