This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
QuickMark is a lightning-fast Markdown/CommonMark linter written in Rust. It's inspired by markdownlint for Ruby and focuses on providing exceptional performance while integrating with development environments through LSP.
The original David Anson's markdownlint source code is available here: https://github.com/DavidAnson/markdownlint
This is a Rust workspace with multiple crates implementing a clean separation of concerns:
quickmark/
├── Cargo.toml # Workspace configuration
├── crates/
│ ├── quickmark-core/ # Core linting logic with integrated configuration
│ │ ├── Cargo.toml
│ │ ├── src/
│ │ │ ├── lib.rs
│ │ │ ├── config/ # Configuration data structures and TOML parsing
│ │ │ ├── linter.rs # Linting engine
│ │ │ ├── rules/ # Individual linting rules
│ │ │ ├── test_utils.rs # Testing utilities
│ │ │ └── tree_sitter_walker.rs # Tree-sitter AST traversal utilities
│ │ └── tests/
│ ├── quickmark-cli/ # CLI application
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── main.rs # CLI interface
│ └── quickmark-server/ # Server application (LSP, etc.)
│ ├── Cargo.toml
│ └── src/
│ └── main.rs # Server interface
├── docs/ # Documentation
├── test-samples/ # Test files and configurations
└── vscode-quickmark/ # VSCode extension
- Build all crates:
cargo build - Build release:
cargo build --release - Run tests:
cargo test - Run CLI linter:
cargo run --bin qmark -- /path/to/file.md - Run server:
cargo run --bin quickmark-server
- QuickMark looks for
quickmark.tomlin the current working directory - If not found, default configuration is used
quickmark-core (Core Library):
- Core linting logic with integrated configuration system
- TOML configuration parsing and validation
- Converts TOML structures to
QuickmarkConfigobjects - Tree-sitter based Markdown parsing
- Rule system with pluggable architecture
- Rule severity normalization and validation
- Self-contained design eliminates external configuration dependencies
quickmark-cli (CLI Application):
- Command-line interface using clap
- File I/O and user interaction
- Uses
quickmark-corefor configuration parsing and linting - Parallel file processing with rayon
- File glob and ignore pattern support
quickmark-server (Server Application):
- LSP server interface for editor integration
- Uses
quickmark-corefor configuration and linting - Async processing with tokio
- Real-time document analysis
Linting Engine (quickmark-core/src/linter.rs):
MultiRuleLinter: Orchestrates multiple rule lintersRuleViolation: Represents a linting error with location and messageContext: Shared context containing file path and configuration- Uses tree-sitter for Markdown parsing with tree-sitter-md grammar
- Filters rules based on severity configuration (off/warn/err)
Configuration System (quickmark-core/src/config/mod.rs):
- Format-agnostic configuration data structures
QuickmarkConfig: Root configuration structureRuleSeverity: Enum for error/warning/off statesnormalize_severities: Validates and normalizes rule configurations- No serialization dependencies - pure data structures
TOML Configuration (quickmark-core/src/config/mod.rs):
- Integrated TOML parsing within the core library
parse_toml_config: Parses TOML strings intoQuickmarkConfigconfig_in_path_or_default: Loads config from filesystem or defaults- TOML-specific data structures with serde derives
- Direct conversion to core configuration types
Rule System (quickmark-core/src/rules/mod.rs):
Rule: Static metadata structure defining rule propertiesALL_RULES: Registry of all available rules- Each rule implements
RuleLintertrait withfeedmethod - Rules are dynamically instantiated based on configuration
Performance-Optimized Single-Pass Design:
QuickMark has evolved from a simple node-based traversal to a sophisticated single-pass architecture that efficiently handles different rule types while maintaining exceptional performance. This design is inspired by the original markdownlint's architecture but leverages Rust's performance advantages and tree-sitter's robust parsing.
Rule Type Classification:
Rules are categorized into five types for optimal performance and implementation strategy:
- Line-Based Rules (e.g., MD013): Operate directly on raw text lines with AST context for configuration
- Token-Based Rules (e.g., MD001, MD003): Work with specific cached AST node types
- Document-Wide Rules (e.g., MD024, MD025): Require full document state analysis
- Hybrid Rules (e.g., MD022): Need both AST analysis and line context for structural spacing
- Special Rules (e.g., MD044): Unique implementation requirements like external dictionaries
Enhanced Context System:
The Context provides multiple optimized data views:
- Raw text lines for line-based analysis
- Cached filtered AST nodes by type (headings, code blocks, etc.)
- Configuration-driven rule execution with lazy evaluation
Motivation for Single-Pass Architecture:
- Performance: Avoids multiple document parsing passes that would compromise QuickMark's speed promise
- Memory Efficiency: Caches commonly-used node types rather than re-filtering AST repeatedly
- Scalability: Supports complex rules (cross-document validation, word analysis) without architectural changes
- Compatibility: Maintains the existing rule interface while enabling performance optimizations
This architecture allows rules like MD013 to work efficiently with raw text while still having access to AST context for proper configuration handling (e.g., different limits for headings vs. code blocks).
Separation of Concerns: Each crate has a single, focused responsibility:
- Core library integrates linting logic with configuration parsing
- Applications handle their specific interfaces (CLI, server)
- Clean dependency hierarchy with core as the foundation
Plugin Architecture: Rules are registered in ALL_RULES and dynamically loaded based on configuration.
Shared Context: Rc<Context> is passed to all rule linters, containing file path and configuration.
Hybrid AST + Line Processing: Uses tree-sitter for structural analysis with cached node filtering, plus direct text line access for line-based rules. Rules receive an enhanced context with multiple optimized data views.
Configuration-Driven: Rule severity and settings are externally configurable via TOML files.
Integrated Configuration: The core library includes TOML configuration parsing while maintaining clean separation between configuration structures and linting logic.
anyhow: Error handlingtree-sitter: AST parsingtree-sitter-md: Markdown grammarserde: TOML deserializationtoml: TOML parsing and configurationregex: Pattern matchinglinkify: URL detectiononce_cell: Lazy statics
anyhow: Error handlingclap: CLI parsing with derive featuresquickmark-core(path = "../quickmark-core"): Linting engine and configurationglob: File pattern matchingrayon: Parallel processingignore: Gitignore-style file filteringwalkdir: Directory traversal
anyhow: Error handlingquickmark-core(path = "../quickmark-core"): Linting engine and configurationtower-lsp: LSP server implementationtokio: Async runtime
- Create a new rule module in
crates/quickmark-core/src/rules/ - Implement the
RuleLintertrait with appropriateRuleTypeclassification - Add the rule to
ALL_RULESincrates/quickmark-core/src/rules/mod.rs - Add any rule-specific configuration to the config structs
- Update TOML parsing in
quickmark-core/src/config/if needed
Rule Type Guidelines:
- Use
RuleType::Linefor rules that primarily analyze text content (line length, whitespace, etc.) - Use
RuleType::Tokenfor rules that analyze document structure (headings, lists, code blocks) - Use
RuleType::Documentfor rules requiring full document analysis (duplicate headings, cross-references) - Use
RuleType::Hybridfor rules needing both AST nodes and line context (blank line spacing around elements) - Use
RuleType::Specialfor rules with unique requirements (external dictionaries, complex text analysis)
- Add conversion functions to
quickmark-core/src/config/ - Implement parsing functions following the pattern of
parse_toml_config - Both CLI and server applications inherit the new format support automatically
- Extend the configuration module with new format-specific dependencies as needed
- Follow idiomatic Rust: Use the latest Rust best practices and conventions. Code should feel natural to an experienced Rust developer.
- No unsafe unless required: Do not use unsafe blocks unless absolutely necessary for performance or interoperability. If used, justify with a comment and encapsulate safely.
- Zero compiler warnings:
- Code must compile with
#![deny(warnings)]. - Suppress only known false positives with clearly scoped
#[allow(...)]attributes and documented reasons.
- Code must compile with
- Speed over memory:
- Optimize for CPU performance, even at the cost of increased memory usage.
- Avoid unnecessary allocations, but favor speed in algorithms and data access patterns.
- Cloning is expensive: Avoid cloning (
.clone()) unless it is proven to be more efficient than passing a reference or performing in-place mutation. - Use modern language features:
- Prefer
let else,if let, match ergonomics, Iterator combinators,?operator, and Result-based error handling. - Consider
CoworArcwhere applicable to avoid unnecessary clones.
- Prefer
- Prefer move semantics when data is no longer needed.
- Use references wisely: Use
&Tor&mut Trather thanTorT.clone()when ownership is not required. - Inline strategically: Inline functions where beneficial using
#[inline](but measure if in doubt). - Use zero-cost abstractions: From the standard library or crates like
itertools,smallvec,rayon(for parallelism), etc. - Choose fast data structures: For hot paths, prefer faster data structures, even if more memory is consumed (e.g.,
HashMapoverBTreeMapwhen ordering is unnecessary).
- Use strong typing: Use the type system to enforce invariants.
- Avoid panics: In library code (
unwrap(),expect()) unless in clearly unreachable branches. - Mark important results: Use
#[must_use]to mark important results when appropriate. - Document assumptions: Document all
TODO,FIXME, and assumptions in code.
Code must pass:
cargo checkcargo clippy --all-targets --all-features -- -D warningscargo fmt --checkcargo test --all
Additional practices:
- Use Clippy lints: That enforce performance best practices (e.g.,
clippy::redundant_clone,clippy::needless_collect,clippy::manual_memcpy, etc.) - Use performance attributes:
#[inline(always)],#[cold], or#[no_mangle]where profiling/FFI suggests it makes sense — but only after benchmarking.
Tests must:
- Cover edge cases: And performance regressions.
- Use
#[should_panic]: Where panics are expected. - Prefer property-based testing: Use
proptestor fuzzing where inputs are highly variable.
Follow the branch naming conventions defined in CONTRIBUTING.md.
When working on a branch that follows the naming convention, always include the issue reference in commit messages:
If the branch name contains an issue number (e.g., feature/123-add-rule), include fixes #123 in the commit message to automatically link and close the issue when merged.
Format:
Brief description of changes
Longer explanation if needed.
Fixes #123
Example:
Add MD025 single H1 rule implementation
Implements the MD025 rule that ensures documents have only one H1 heading.
Includes comprehensive tests and documentation.
Fixes #123