use mdbook_lint::{Config, Document, LintEngine, PluginRegistry};
use mdbook_lint_rulesets::{MdBookRuleProvider, StandardRuleProvider};
use std::time::{Duration, Instant};
fn create_lint_engine() -> LintEngine {
let mut registry = PluginRegistry::new();
registry
.register_provider(Box::new(StandardRuleProvider))
.unwrap();
registry
.register_provider(Box::new(MdBookRuleProvider))
.unwrap();
registry.create_engine().unwrap()
}
fn create_test_document(content: &str) -> Document {
Document::new(content.to_string(), "test.md".into()).unwrap()
}
fn assert_completes_quickly(
engine: &LintEngine,
document: &Document,
max_duration: Duration,
description: &str,
) {
let start = Instant::now();
let result = engine.lint_document_with_config(document, &Config::default().core);
let elapsed = start.elapsed();
println!(" {} took {:?}", description, elapsed);
assert!(
elapsed < max_duration,
"{} took {:?} but should complete within {:?}",
description,
elapsed,
max_duration
);
assert!(result.is_ok(), "Linting should not fail: {:?}", result);
}
#[test]
fn test_performance_regression_md051_html_fragments() {
let engine = create_lint_engine();
let html_content = r##"# Test Document
This document tests MD051 performance with HTML fragments.
<a href="#section1">Link 1</a>
<a href="#section2">Link 2</a>
<a href="#section3">Link 3</a>
<a href="#section4">Link 4</a>
<a href="#section5">Link 5</a>
## Section 1 {#section1}
Content here.
## Section 2 {#section2}
More content.
## Section 3 {#section3}
Even more content.
## Section 4 {#section4}
Content continues.
## Section 5 {#section5}
Final content.
"##;
let document = create_test_document(html_content);
assert_completes_quickly(
&engine,
&document,
Duration::from_millis(150),
"MD051 HTML fragment test",
);
}
#[test]
fn test_performance_regression_md049_emphasis_patterns() {
let engine = create_lint_engine();
let emphasis_content = r##"# Test Document
This tests MD049 with patterns that previously caused issues:
- `wrapping_*` function calls in code
- `checked_*` operations in inline code
- `saturating_*` arithmetic in backticks
- Normal *emphasis* outside code should work
- Multiple `code_*` patterns in the same line
- Mixed patterns: `first_*` and *emphasis* and `second_*`
```rust
// Code blocks should not be affected
fn wrapping_add(a: u32, b: u32) -> u32 {
a.wrapping_add(b)
}
```
More text with `inline_*` patterns and *real emphasis*.
"##;
let document = create_test_document(emphasis_content);
assert_completes_quickly(
&engine,
&document,
Duration::from_millis(50),
"MD049 emphasis pattern test",
);
}
#[test]
fn test_performance_large_document() {
let engine = create_lint_engine();
let mut large_content = String::with_capacity(50_000);
large_content.push_str("# Large Document Performance Test\n\n");
for i in 1..=200 {
large_content.push_str(&format!(
"## Section {}\n\nThis is content for section {}. It contains some *emphasis* and `code` examples.\n\n",
i, i
));
for j in 1..=5 {
large_content.push_str(&format!("- List item {} in section {}\n", j, i));
}
large_content.push('\n');
if i % 10 == 0 {
large_content.push_str(&format!(
"```rust\n// Code example for section {}\nfn example_{}() {{\n println!(\"Section {}\");\n}}\n```\n\n",
i, i, i
));
}
}
let document = create_test_document(&large_content);
println!("Testing document with {} characters", large_content.len());
assert_completes_quickly(
&engine,
&document,
Duration::from_millis(500),
"Large document test",
);
}
#[test]
fn test_performance_deeply_nested_content() {
let engine = create_lint_engine();
let mut nested_content = String::new();
nested_content.push_str("# Deep Nesting Test\n\n");
for level in 0..50 {
let indent = " ".repeat(level);
nested_content.push_str(&format!(
"{}* Level {} item with *emphasis*\n",
indent,
level + 1
));
}
nested_content.push('\n');
for level in 1..=20 {
let prefix = "> ".repeat(level);
nested_content.push_str(&format!("{}Quote at level {} with `code`\n", prefix, level));
}
let document = create_test_document(&nested_content);
assert_completes_quickly(
&engine,
&document,
Duration::from_millis(200),
"Deep nesting test",
);
}
#[test]
fn test_performance_many_violations() {
let engine = create_lint_engine();
let violations_content = r#"# Test Document
### Skipped H2 (MD001 violation)
Line with trailing spaces
Another line with trailing spaces
```
Code block without language (MD040/MDBOOK001 violation)
some code here
```
This line is intentionally very long to trigger MD013 line length violations and should exceed the typical 80 character limit significantly
# Multiple spaces after hash (MD018 violation)
##Heading without space (MD018 violation)
- List item 1
* Mixed list markers (MD004 violation)
- List item 3
[Bad link](
**Unclosed emphasis
Multiple blank lines below:
Above were multiple blank lines (MD012 violation)
"#;
let document = create_test_document(violations_content);
assert_completes_quickly(
&engine,
&document,
Duration::from_millis(100),
"Many violations test",
);
}
#[test]
fn test_performance_pathological_input() {
let engine = create_lint_engine();
let pathological_inputs = [
&format!("# Test\n\n{}", "x".repeat(10000)),
&format!("# Test\n{}", "\n".repeat(1000)),
&"[link]".repeat(1000),
"# Test\r\nContent\nMore\r\nContent\n",
"# Test\n\nこんにちは世界 🌍 مرحبا بالعالم עולם שלום",
&format!("{}text{}", "*".repeat(100), "*".repeat(100)),
];
for (i, input) in pathological_inputs.iter().enumerate() {
let document = create_test_document(input);
assert_completes_quickly(
&engine,
&document,
Duration::from_millis(300),
&format!("Pathological input #{}", i + 1),
);
}
}