mdbook_lint_core/lib.rs
1//! Core linting engine for mdbook-lint
2//!
3//! This crate provides the foundational infrastructure for markdown linting with mdBook support.
4//! It defines the core abstractions and engine that powers mdbook-lint's rule-based linting system.
5//!
6//! # Overview
7//!
8//! The `mdbook-lint-core` crate provides:
9//! - **Plugin-based architecture** for extensible rule sets
10//! - **AST and text-based linting** with efficient document processing
11//! - **Violation reporting** with detailed position tracking and severity levels
12//! - **Automatic fix infrastructure** for correctable violations
13//! - **Configuration system** for customizing rule behavior
14//! - **Document abstraction** with markdown parsing via comrak
15//!
16//! # Architecture
17//!
18//! The core follows a plugin-based architecture where rules are provided by external crates:
19//!
20//! ```text
21//! ┌─────────────────┐
22//! │ Application │
23//! └────────┬────────┘
24//! │
25//! ┌────────▼────────┐
26//! │ PluginRegistry │ ◄─── Registers rule providers
27//! └────────┬────────┘
28//! │
29//! ┌────────▼────────┐
30//! │ LintEngine │ ◄─── Orchestrates linting
31//! └────────┬────────┘
32//! │
33//! ┌────────▼────────┐
34//! │ Rules │ ◄─── Individual rule implementations
35//! └─────────────────┘
36//! ```
37//!
38//! # Basic Usage
39//!
40//! ## Creating a Lint Engine
41//!
42//! ```rust
43//! use mdbook_lint_core::{PluginRegistry, Document};
44//! use std::path::PathBuf;
45//!
46//! // Create an empty engine (no rules registered)
47//! let registry = PluginRegistry::new();
48//! let engine = registry.create_engine()?;
49//!
50//! // Lint a document
51//! let document = Document::new("# Hello\n\nWorld".to_string(), PathBuf::from("test.md"))?;
52//! let violations = engine.lint_document(&document)?;
53//!
54//! // No violations since no rules are registered
55//! assert_eq!(violations.len(), 0);
56//! # Ok::<(), Box<dyn std::error::Error>>(())
57//! ```
58//!
59//! ## With Rule Providers
60//!
61//! ```rust,no_run
62//! use mdbook_lint_core::{PluginRegistry, Document};
63//! // Assumes mdbook-lint-rulesets is available
64//! // use mdbook_lint_rulesets::{StandardRuleProvider, MdBookRuleProvider};
65//! use std::path::PathBuf;
66//!
67//! let mut registry = PluginRegistry::new();
68//!
69//! // Register rule providers
70//! // registry.register_provider(Box::new(StandardRuleProvider))?;
71//! // registry.register_provider(Box::new(MdBookRuleProvider))?;
72//!
73//! // Create engine with registered rules
74//! let engine = registry.create_engine()?;
75//!
76//! // Lint a document
77//! let content = "# Title\n\n\n\nToo many blank lines";
78//! let document = Document::new(content.to_string(), PathBuf::from("test.md"))?;
79//! let violations = engine.lint_document(&document)?;
80//!
81//! // Process violations
82//! for violation in violations {
83//! println!("{}:{} - {}", violation.rule_id, violation.line, violation.message);
84//! }
85//! # Ok::<(), Box<dyn std::error::Error>>(())
86//! ```
87//!
88//! # Key Types
89//!
90//! ## Document
91//!
92//! Represents a markdown file with its content and metadata:
93//!
94//! ```rust
95//! use mdbook_lint_core::Document;
96//! use std::path::PathBuf;
97//! use comrak::Arena;
98//!
99//! let doc = Document::new(
100//! "# My Document\n\nContent here".to_string(),
101//! PathBuf::from("doc.md")
102//! )?;
103//!
104//! // Parse AST with comrak Arena
105//! let arena = Arena::new();
106//! let ast = doc.parse_ast(&arena);
107//!
108//! // Get document lines
109//! let lines = &doc.lines;
110//! # Ok::<(), Box<dyn std::error::Error>>(())
111//! ```
112//!
113//! ## Violation
114//!
115//! Represents a linting violation with location and optional fix:
116//!
117//! ```rust
118//! use mdbook_lint_core::violation::{Violation, Severity, Fix, Position};
119//!
120//! let violation = Violation {
121//! rule_id: "MD001".to_string(),
122//! rule_name: "heading-increment".to_string(),
123//! message: "Heading levels should increment by one".to_string(),
124//! line: 5,
125//! column: 1,
126//! severity: Severity::Warning,
127//! fix: Some(Fix {
128//! description: "Change heading level".to_string(),
129//! replacement: Some("## Correct Level".to_string()),
130//! start: Position { line: 5, column: 1 },
131//! end: Position { line: 5, column: 20 },
132//! }),
133//! };
134//! ```
135//!
136//! ## Rule Traits
137//!
138//! Rules can be implemented using different traits based on their needs:
139//!
140//! - `Rule` - Base trait for all rules
141//! - `AstRule` - For rules that analyze the markdown AST
142//! - `TextRule` - For rules that analyze raw text
143//! - `RuleWithConfig` - For rules that support configuration
144//!
145//! # Configuration
146//!
147//! Rules can be configured through TOML configuration files:
148//!
149//! ```toml
150//! # .mdbook-lint.toml
151//! [rules.MD013]
152//! line_length = 120
153//! code_blocks = false
154//!
155//! [rules.MD009]
156//! br_spaces = 2
157//!
158//! # Disable specific rules
159//! [rules]
160//! MD002 = false
161//! MD041 = false
162//! ```
163//!
164//! # Features
165//!
166//! This crate has no optional features. All functionality is included by default.
167
168pub mod config;
169pub mod deduplication;
170pub mod document;
171pub mod engine;
172pub mod error;
173pub mod registry;
174pub mod rule;
175pub mod test_helpers;
176pub mod violation;
177
178// Re-export core types for convenience
179pub use config::Config;
180pub use document::Document;
181pub use engine::{LintEngine, PluginRegistry, RuleProvider};
182pub use error::{
183 ConfigError, DocumentError, ErrorContext, IntoMdBookLintError, MdBookLintError, MdlntError,
184 PluginError, Result, RuleError,
185};
186pub use registry::RuleRegistry;
187pub use rule::{AstRule, Rule, RuleCategory, RuleMetadata, RuleStability};
188pub use violation::{Severity, Violation};
189
190/// Current version of mdbook-lint-core
191pub const VERSION: &str = env!("CARGO_PKG_VERSION");
192
193/// Human-readable name
194pub const NAME: &str = "mdbook-lint-core";
195
196/// Description
197pub const DESCRIPTION: &str = "Core linting engine for mdbook-lint";
198
199/// Create a lint engine with all available rules (standard + mdBook)
200/// Note: Requires mdbook-lint-rulesets dependency for rule providers
201pub fn create_engine_with_all_rules() -> LintEngine {
202 panic!(
203 "create_engine_with_all_rules() is deprecated. Use mdbook-lint-rulesets crate providers directly."
204 )
205}
206
207/// Create a lint engine with only standard markdown rules
208/// Note: Requires mdbook-lint-rulesets dependency for rule providers
209pub fn create_standard_engine() -> LintEngine {
210 panic!(
211 "create_standard_engine() is deprecated. Use mdbook-lint-rulesets crate providers directly."
212 )
213}
214
215/// Create a lint engine with only mdBook-specific rules
216/// Note: Requires mdbook-lint-rulesets dependency for rule providers
217pub fn create_mdbook_engine() -> LintEngine {
218 panic!(
219 "create_mdbook_engine() is deprecated. Use mdbook-lint-rulesets crate providers directly."
220 )
221}
222
223/// Common imports
224pub mod prelude {
225 pub use crate::{
226 Document,
227 engine::{LintEngine, PluginRegistry, RuleProvider},
228 error::{ErrorContext, IntoMdBookLintError, MdBookLintError, MdlntError, Result},
229 registry::RuleRegistry,
230 rule::{AstRule, Rule, RuleCategory, RuleMetadata, RuleStability},
231 violation::{Severity, Violation},
232 };
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_version_info() {
241 assert_eq!(NAME, "mdbook-lint-core");
242 assert!(DESCRIPTION.contains("linting engine"));
243 }
244
245 #[test]
246 #[should_panic]
247 fn test_create_all_rules_engine_deprecated() {
248 create_engine_with_all_rules();
249 }
250
251 #[test]
252 #[should_panic]
253 fn test_create_standard_engine_deprecated() {
254 create_standard_engine();
255 }
256
257 #[test]
258 #[should_panic]
259 fn test_create_mdbook_engine_deprecated() {
260 create_mdbook_engine();
261 }
262
263 #[test]
264 fn test_basic_engine_creation() {
265 let engine = LintEngine::new();
266 assert_eq!(engine.available_rules().len(), 0);
267 }
268
269 #[test]
270 fn test_plugin_registry_creation() {
271 let registry = PluginRegistry::new();
272 assert_eq!(registry.providers().len(), 0);
273 }
274
275 #[test]
276 fn test_rule_registry_creation() {
277 let registry = RuleRegistry::new();
278 assert!(registry.is_empty());
279 }
280}