Trait AstRule

Source
pub trait AstRule: Send + Sync {
    // Required methods
    fn id(&self) -> &'static str;
    fn name(&self) -> &'static str;
    fn description(&self) -> &'static str;
    fn metadata(&self) -> RuleMetadata;
    fn check_ast<'a>(
        &self,
        document: &Document,
        ast: &'a AstNode<'a>,
    ) -> Result<Vec<Violation>>;

    // Provided methods
    fn can_fix(&self) -> bool { ... }
    fn fix(&self, _content: &str, _violation: &Violation) -> Option<String> { ... }
    fn create_violation(
        &self,
        message: String,
        line: usize,
        column: usize,
        severity: Severity,
    ) -> Violation { ... }
}
Expand description

Helper trait for AST-based rules

§When to Use AstRule vs Rule

Use AstRule when your rule needs to:

  • Analyze document structure (headings, lists, links, code blocks)
  • Navigate parent-child relationships in the markdown tree
  • Access precise position information from comrak’s sourcepos
  • Understand markdown semantics beyond simple text patterns

Use Rule directly when your rule:

  • Only needs line-by-line text analysis
  • Checks simple text patterns (trailing spaces, line length)
  • Doesn’t need to understand markdown structure

§Implementation Examples

AstRule Examples:

  • MD001 (heading-increment): Needs to traverse heading hierarchy
  • MDBOOK002 (link-validation): Needs to find and validate link nodes
  • MD031 (blanks-around-fences): Needs to identify fenced code blocks

Rule Examples:

  • MD013 (line-length): Simple line-by-line character counting
  • MD009 (no-trailing-spaces): Pattern matching on line endings

§Basic Implementation Pattern

use mdbook_lint_core::rule::{AstRule, RuleMetadata, RuleCategory};
use mdbook_lint_core::{Document, Violation, Result};
use comrak::nodes::{AstNode, NodeValue};

pub struct MyRule;

impl AstRule for MyRule {
    fn id(&self) -> &'static str { "MY001" }
    fn name(&self) -> &'static str { "my-rule" }
    fn description(&self) -> &'static str { "Description of what this rule checks" }

    fn metadata(&self) -> RuleMetadata {
        RuleMetadata::stable(RuleCategory::Structure)
    }

    fn check_ast<'a>(&self, document: &Document, ast: &'a AstNode<'a>) -> Result<Vec<Violation>> {
        let mut violations = Vec::new();

        // Find nodes of interest
        for node in ast.descendants() {
            if let NodeValue::Heading(heading) = &node.data.borrow().value {
                // Get position information
                if let Some((line, column)) = document.node_position(node) {
                    // Check some condition
                    if heading.level > 3 {
                        violations.push(self.create_violation(
                            "Heading too deep".to_string(),
                            line,
                            column,
                            mdbook_lint_core::violation::Severity::Warning,
                        ));
                    }
                }
            }
        }

        Ok(violations)
    }
}

§Key Methods Available

From Document:

  • document.node_position(node) - Get (line, column) for any AST node
  • document.node_text(node) - Extract text content from a node
  • document.headings(ast) - Get all heading nodes
  • document.code_blocks(ast) - Get all code block nodes

From AstNode:

  • node.descendants() - Iterate all child nodes recursively
  • node.children() - Get direct children only
  • node.parent() - Get parent node (if any)
  • node.data.borrow().value - Access the NodeValue enum

Creating Violations:

  • self.create_violation(message, line, column, severity) - Standard violation creation

Required Methods§

Source

fn id(&self) -> &'static str

Unique identifier for the rule (e.g., “MD001”)

Source

fn name(&self) -> &'static str

Human-readable name for the rule (e.g., “heading-increment”)

Source

fn description(&self) -> &'static str

Description of what the rule checks

Source

fn metadata(&self) -> RuleMetadata

Metadata about this rule’s status and properties

Source

fn check_ast<'a>( &self, document: &Document, ast: &'a AstNode<'a>, ) -> Result<Vec<Violation>>

Check a document using its AST

Provided Methods§

Source

fn can_fix(&self) -> bool

Whether this rule can automatically fix violations

Source

fn fix(&self, _content: &str, _violation: &Violation) -> Option<String>

Attempt to fix a violation (if supported)

Source

fn create_violation( &self, message: String, line: usize, column: usize, severity: Severity, ) -> Violation

Create a violation for this rule

Implementors§