use criterion::{Criterion, criterion_group, criterion_main};
use rumdl_lib::config::MarkdownFlavor;
use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::{
MD013LineLength, MD033NoInlineHtml, MD037NoSpaceInEmphasis, MD044ProperNames, MD051LinkFragments,
MD053LinkImageReferenceDefinitions,
};
use std::hint::black_box;
fn make_ctx(content: &str) -> LintContext<'_> {
LintContext::new(content, MarkdownFlavor::Standard, None)
}
fn bench_md013(c: &mut Criterion) {
let mut content = String::with_capacity(50_000);
for i in 0..100 {
content.push_str(&format!("Line {:03} with a very long text that exceeds the default line length limit of 80 characters. This line is exactly 120 characters.{}\n", i, " ".repeat(5)));
}
let rule = MD013LineLength::default();
let ctx = make_ctx(&content);
c.bench_function("MD013 check 100 long lines", |b| b.iter(|| rule.check(black_box(&ctx))));
c.bench_function("MD013 fix 100 long lines", |b| b.iter(|| rule.fix(black_box(&ctx))));
}
fn bench_md033(c: &mut Criterion) {
let mut content = String::with_capacity(50_000);
for i in 0..500 {
content.push_str(&format!(
"Line {i} with <span class=\"highlight\">HTML</span> and <div>nested <em>tags</em></div>\n"
));
}
let rule = MD033NoInlineHtml::default();
let ctx = make_ctx(&content);
c.bench_function("MD033 check 500 HTML tags", |b| b.iter(|| rule.check(black_box(&ctx))));
}
fn bench_md037(c: &mut Criterion) {
let mut content = String::with_capacity(50_000);
for i in 0..500 {
if i % 3 == 0 {
content.push_str(&format!("Line {i} with*incorrect emphasis*markers\n"));
} else if i % 3 == 1 {
content.push_str(&format!("Line {i} with *incorrect emphasis *markers\n"));
} else {
content.push_str(&format!("Line {i} with *correct emphasis* markers\n"));
}
}
let rule = MD037NoSpaceInEmphasis;
let ctx = make_ctx(&content);
c.bench_function("MD037 check 500 emphasis markers", |b| {
b.iter(|| rule.check(black_box(&ctx)))
});
c.bench_function("MD037 fix 500 emphasis markers", |b| {
b.iter(|| rule.fix(black_box(&ctx)))
});
}
fn bench_md044(c: &mut Criterion) {
let mut content = String::with_capacity(50_000);
let proper_names = vec![
"JavaScript".to_string(),
"TypeScript".to_string(),
"GitHub".to_string(),
"VS Code".to_string(),
"Docker".to_string(),
"Kubernetes".to_string(),
];
let incorrect_names = ["javascript", "typescript", "github", "vs code", "docker", "kubernetes"];
for i in 0..500 {
if i % 2 == 0 {
let name_idx = i % proper_names.len();
content.push_str(&format!("Line {} mentions {} correctly\n", i, proper_names[name_idx]));
} else {
let name_idx = i % incorrect_names.len();
content.push_str(&format!(
"Line {} mentions {} incorrectly\n",
i, incorrect_names[name_idx]
));
}
}
let rule = MD044ProperNames::new(proper_names, true); let ctx = make_ctx(&content);
c.bench_function("MD044 check 500 proper name occurrences", |b| {
b.iter(|| rule.check(black_box(&ctx)))
});
c.bench_function("MD044 fix 500 proper name occurrences", |b| {
b.iter(|| rule.fix(black_box(&ctx)))
});
}
fn bench_md051(c: &mut Criterion) {
let mut content = String::with_capacity(50_000);
for i in 1..101 {
content.push_str(&format!("## Heading {i}\n\n"));
}
for i in 0..500 {
if i % 3 == 0 {
let heading_number = (i % 100) + 1;
content.push_str(&format!("This is a [valid link](#heading-{heading_number})\n"));
} else {
content.push_str(&format!("This is an [invalid link](#non-existent-heading-{i})\n"));
}
}
let rule = MD051LinkFragments::new();
let ctx = make_ctx(&content);
c.bench_function("MD051 check 500 link fragments", |b| {
b.iter(|| rule.check(black_box(&ctx)))
});
}
fn bench_md053(c: &mut Criterion) {
let mut content = String::with_capacity(50_000);
content.push_str("## Reference Definitions\n\n");
for i in 0..200 {
if i < 100 {
content.push_str(&format!("[ref{i}]: https://example.com/ref{i}\n"));
} else {
content.push_str(&format!("[unused{i}]: https://example.com/unused{i}\n"));
}
}
content.push_str("\n## Content with References\n\n");
for i in 0..100 {
content.push_str(&format!("This is a paragraph with a [link][ref{i}] reference.\n"));
}
let rule = MD053LinkImageReferenceDefinitions::default();
let ctx = make_ctx(&content);
c.bench_function("MD053 check cold cache", |b| {
b.iter_with_setup(MD053LinkImageReferenceDefinitions::default, |r| {
r.check(black_box(&ctx))
})
});
let primed_rule = rule.clone();
let _ = primed_rule.check(&ctx);
c.bench_function("MD053 check warm cache", |b| {
b.iter(|| primed_rule.check(black_box(&ctx)))
});
c.bench_function("MD053 fix unused references", |b| b.iter(|| rule.fix(black_box(&ctx))));
}
criterion_group!(
benches,
bench_md013,
bench_md033,
bench_md037,
bench_md044,
bench_md051,
bench_md053
);
criterion_main!(benches);