# Clone the repository
git clone https://github.com/docToolchain/asciidoc-linter.git
# Navigate to the project directory
cd asciidoc-linter
# Install the package
pip install .
AsciiDoc Linter Manual
Introduction
About the Project
AsciiDoc Linter is a Python-based tool designed to help maintain high-quality AsciiDoc documentation. It checks your AsciiDoc files for common issues and style violations, helping teams maintain consistent documentation standards.
As part of the docToolchain project (https://doctoolchain.org), it integrates well with existing documentation workflows.
Key Features
-
Heading structure validation
-
Proper hierarchy (no skipped levels)
-
Consistent formatting
-
Single top-level heading
-
-
Block validation
-
Proper block termination
-
Consistent spacing
-
-
Whitespace consistency
-
Line spacing
-
List formatting
-
Tab detection
-
-
Image validation
-
Attribute checking
-
File existence verification
-
-
Multiple output formats (console, JSON, HTML)
Project Goals
-
Improve documentation quality through automated checks
-
Enforce consistent styling across documentation
-
Reduce manual review effort
-
Catch common mistakes early in the documentation process
-
Support documentation as code practices
-
Integrate with existing documentation toolchains
Technical Overview
| Component | Description |
|---|---|
Language |
Python 3.8+ |
Testing |
unittest framework |
Documentation |
AsciiDoc |
Configuration |
YAML/JSON (planned) |
Getting Started
Prerequisites
-
Python 3.8 or higher
-
Git (for installation)
Installation
|
Direct installation via pip is planned for future releases. Currently, installation is done via git clone. |
Basic Usage
# Check a single file
asciidoc-linter document.adoc
# Check multiple files
asciidoc-linter doc1.adoc doc2.adoc
# Get help
asciidoc-linter --help
Current Status
Implemented Features
-
Core linting engine
-
Basic rule set (headings, blocks, whitespace, images)
-
Command-line interface
-
Multiple output formats
Planned Features
-
Configuration system (YAML/JSON)
-
Additional rule sets (tables, links, cross-references)
-
Direct installation via pip
-
IDE integration
-
Git pre-commit hooks
Development Guide
Setting Up Development Environment
Clone the Repository
git clone https://github.com/docToolchain/asciidoc-linter.git
cd asciidoc-linter
Create Virtual Environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -e .
Project Structure
asciidoc-linter/
├── asciidoc_linter/ # Main package
│ ├── __init__.py
│ ├── cli.py # Command line interface
│ ├── rules/ # Rule implementations
│ │ ├── __init__.py
│ │ ├── base.py # Base classes for rules
│ │ ├── heading_rules.py
│ │ ├── block_rules.py
│ │ ├── whitespace_rules.py
│ │ └── image_rules.py
│ ├── parser.py # AsciiDoc parser
│ └── reporter.py # Output formatters
├── tests/ # Test files
│ └── rules/ # Rule-specific tests
├── docs/ # Documentation
└── README.adoc
Current Implementation Status
Implemented Features
-
Core rule engine with base classes
-
Rule implementations:
-
Heading rules (hierarchy, format, multiple top-level)
-
Block rules (termination, spacing)
-
Whitespace rules (spacing, formatting)
-
Image rules (attributes, file verification)
-
-
Basic command line interface
-
Multiple output formats (console, JSON, HTML)
-
Comprehensive test suite
Planned Features
-
Configuration system (YAML/JSON)
-
Rule enabling/disabling
-
Severity customization
-
Custom rule parameters
-
-
Additional rule types
-
IDE integration
-
Git hooks
Adding New Rules
Rule Implementation Steps
-
Create a new rule class:
from .base import Rule, Finding, Severity, Position class MyNewRule(Rule): """ RULE_ID: Short description. Detailed explanation of what the rule checks. """ def __init__(self): super().__init__() self.id = "RULE_ID" @property def description(self) -> str: return "Description of what this rule checks" def check(self, content: str) -> List[Finding]: findings = [] # Implementation here return findings -
Add tests for the rule:
class TestMyNewRule(unittest.TestCase): def setUp(self): self.rule = MyNewRule() def test_valid_case(self): content = "Valid content" findings = self.rule.check(content) self.assertEqual(len(findings), 0) def test_invalid_case(self): content = "Invalid content" findings = self.rule.check(content) self.assertEqual(len(findings), 1) self.assertEqual(findings[0].rule_id, "RULE_ID") -
Register the rule in the linter
-
Update documentation
Rule Guidelines
-
Clear rule IDs and descriptions
-
Meaningful error messages
-
Proper severity levels
-
Contextual information in findings
-
Comprehensive test cases
-
Documentation with examples
Code Style
Python Guidelines
-
Follow PEP 8
-
Use type hints
-
Write docstrings (Google style)
-
Keep functions focused and testable
-
Maximum line length: 100 characters
-
Use meaningful variable names
Documentation Guidelines
-
Use AsciiDoc format
-
Include examples for all features
-
Explain error messages
-
Document configuration options
-
Keep README.adoc up to date
Testing
Running Tests
# Run all tests
python run_tests.py
# Run specific test file
python -m unittest tests/rules/test_heading_rules.py
# Run specific test case
python -m unittest tests.rules.test_heading_rules.TestHeadingHierarchyRule
Test Guidelines
-
Write tests for all new features
-
Include both positive and negative test cases
-
Test edge cases
-
Maintain high test coverage
-
Use meaningful test names
Pull Request Process
-
Create feature branch
-
Implement changes
-
Add/update tests
-
Update documentation
-
Run full test suite
-
Submit PR
Release Process
-
Update version number in init.py
-
Update changelog
-
Run full test suite
-
Create release notes
-
Tag release
-
Build and publish
Getting Help
-
GitHub Issues: https://github.com/docToolchain/asciidoc-linter/issues
-
Project Wiki: https://github.com/docToolchain/asciidoc-linter/wiki
-
docToolchain Community: https://doctoolchain.org/community
Testing Strategy and Guide
..1. Test Strategy
Goals and Objectives
The testing strategy for the AsciiDoc Linter aims to:
-
Ensure reliable detection of AsciiDoc formatting issues
-
Prevent false positives that could frustrate users
-
Maintain high code quality through comprehensive testing
-
Enable safe refactoring through good test coverage
-
Support rapid development through automated testing
Test Levels
Unit Tests
-
Test individual rules in isolation
-
Verify rule logic and error detection
-
Cover edge cases and special scenarios
-
Test configuration options
Integration Tests
-
Test interaction between parser and rules
-
Verify correct document processing
-
Test CLI interface and options
-
Test reporter output formats
System Tests
-
End-to-end testing of the linter
-
Test with real AsciiDoc documents
-
Verify correct error reporting
-
Test performance with large documents
Test Coverage Goals
| Component | Target Coverage | Current Coverage |
|---|---|---|
Core (parser, linter) |
90% |
0% |
Rules |
95% |
88% |
CLI |
80% |
0% |
Reporter |
85% |
0% |
Overall |
90% |
61% |
Quality Metrics
-
Line Coverage: Minimum 90%
-
Branch Coverage: Minimum 85%
-
Mutation Score: Minimum 80%
-
Test Success Rate: 100%
-
No known bugs in production
..2. Test Implementation
Test Organization
tests/
├── __init__.py
├── rules/ # Rule-specific tests
│ ├── __init__.py
│ ├── test_heading_rules.py
│ ├── test_block_rules.py
│ ├── test_image_rules.py
│ └── test_whitespace_rules.py
├── integration/ # Integration tests
│ ├── __init__.py
│ ├── test_parser_rules.py
│ └── test_cli_reporter.py
├── system/ # System tests
│ ├── __init__.py
│ ├── test_large_docs.py
│ └── test_real_projects.py
├── test_cli.py # CLI tests
├── test_parser.py # Parser tests
├── test_reporter.py # Reporter tests
└── test_linter.py # Core linter tests
Test Patterns
Rule Tests
def test_rule_pattern(self):
# Given: Setup test data and context
content = "test content"
rule = TestRule(config)
# When: Execute the rule
findings = rule.check(content)
# Then: Verify results
assert_findings(findings)
Integration Tests
def test_integration_pattern(self):
# Given: Setup test environment
doc = create_test_document()
linter = setup_linter()
# When: Process document
results = linter.process(doc)
# Then: Verify complete workflow
verify_results(results)
Test Data Management
Test Documents
-
Maintain a collection of test documents
-
Include both valid and invalid examples
-
Document the purpose of each test file
-
Version control test data
Test Fixtures
-
Use pytest fixtures for common setup
-
Share test data between related tests
-
Clean up test environment after each test
-
Mock external dependencies
..3. Running Tests
Local Development
# Run all tests
python run_tests.py
# Run with coverage
coverage run -m pytest
coverage report
coverage html
# Run specific test categories
pytest tests/rules/
pytest tests/integration/
pytest tests/system/
Continuous Integration
GitHub Actions Workflow
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, "3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
coverage run -m pytest
coverage report
coverage xml
- name: Upload coverage
uses: codecov/codecov-action@v2
..4. Test Maintenance
Regular Activities
-
Review test coverage reports weekly
-
Update tests for new features
-
Refactor tests when needed
-
Review test performance
-
Update test documentation
Quality Checks
-
Run mutation testing monthly
-
Review test maintainability
-
Check for flaky tests
-
Verify test isolation
..5. Appendix
Test Templates
Unit Test Template
class TestRuleName(unittest.TestCase):
def setUp(self):
"""Setup test environment"""
self.rule = RuleUnderTest()
def test_valid_case(self):
"""Test with valid input"""
# Given
content = "valid content"
# When
findings = self.rule.check(content)
# Then
self.assertEqual(len(findings), 0)
def test_invalid_case(self):
"""Test with invalid input"""
# Given
content = "invalid content"
# When
findings = self.rule.check(content)
# Then
self.assertEqual(len(findings), 1)
self.assertEqual(findings[0].severity, Severity.ERROR)
Test Checklists
New Feature Checklist
-
Unit tests written
-
Integration tests updated
-
System tests verified
-
Coverage goals met
-
Documentation updated
Test Review Checklist
-
Tests follow patterns
-
Coverage adequate
-
Edge cases covered
-
Error cases tested
-
Documentation clear
..6. References
.1. Usage Guide
.1.1. Command Line Interface
Basic Usage
# Check a single file
asciidoc-linter document.adoc
# Check multiple files
asciidoc-linter doc1.adoc doc2.adoc
# Check with specific output format
asciidoc-linter --format json document.adoc
# Use specific configuration
asciidoc-linter --config my-config.yml document.adoc
# Enable verbose output
asciidoc-linter --verbose document.adoc
# Enable debug output
asciidoc-linter --debug document.adoc
Command Line Options
| Option | Default | Description |
|---|---|---|
--format |
console |
Output format (console, json, html) |
--config |
None |
Path to configuration file |
--verbose |
False |
Enable verbose output |
--debug |
False |
Enable debug output |
--quiet |
False |
Suppress non-error output |
.1.2. Configuration
Configuration File
# .asciidoc-lint.yml
rules:
HEAD001:
enabled: true
severity: error
HEAD002:
enabled: true
severity: warning
WS001:
enabled: false
severity: warning
Rule Configuration
-
Enable/disable rules
-
Set severity levels
-
Configure rule-specific options
-
Set file patterns
.1.3. Output Formats
Console Output
document.adoc:15 ERROR: Heading level skipped
document.adoc:23 WARNING: Heading should start with uppercase
JSON Output
{
"findings": [
{
"rule": "HEAD001",
"severity": "error",
"message": "Heading level skipped",
"line": 15
}
]
}
HTML Report
Generates a detailed HTML report with:
-
Summary statistics
-
Detailed findings
-
Source context
-
Rule explanations
.1.4. Integration
Git Pre-commit Hook
#!/bin/sh
files=$(git diff --cached --name-only --diff-filter=ACM | grep '.adoc$')
if [ -n "$files" ]; then
asciidoc-linter $files
fi
CI/CD Integration
name: Lint AsciiDoc
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Lint Documentation
run: |
pip install asciidoc-linter
asciidoc-linter docs/
.1.5. Best Practices
Document Organization
-
Use consistent heading levels
-
Add blank lines around blocks
-
Use proper formatting
-
Include alt text for images
Error Resolution
| Issue | Solution |
|---|---|
Skipped heading level |
Ensure heading levels increment by one |
Missing space after = |
Add space after heading markers |
Multiple top-level headings |
Use only one level-1 heading per document |
.1.6. Troubleshooting
Common Problems
-
Configuration file not found
-
Rule conflicts
-
Performance issues
-
False positives
Debug Mode
# Enable debug output
asciidoc-linter --debug document.adoc
# Show rule processing details
asciidoc-linter --verbose document.adoc
.2. AsciiDoc Linter Rules
.2.1. Heading Rules
HEAD001: Heading Hierarchy
Ensures proper heading level incrementation (no skipped levels).
Description
This rule checks that heading levels are not skipped. For example, you cannot go from a level 1 heading (=) directly to a level 3 heading (===) without having a level 2 heading (==) in between.
Examples
== Document Title (Level 1)
=== Section (Level 2)
==== Subsection (Level 3)
=== Another Section (Level 2)
== Document Title (Level 1)
==== Subsection (Level 3) // Error: Skipped Level 2
HEAD002: Heading Format
Ensures proper heading format (spacing and capitalization).
Description
This rule checks two aspects of heading format: 1. There must be a space after the = characters 2. The heading text should start with an uppercase letter
Examples
== Document Title
=== Section Title
==== Subsection Title
=Document Title // Error: No space after =
=== section title // Warning: Starts with lowercase
HEAD003: Multiple Top Level Headings
Ensures document has only one top-level heading.
Description
This rule checks that there is only one level 1 heading (=) in the document. Multiple top-level headings can indicate structural problems or accidentally split documents.
Examples
== Main Document Title
=== Section One
=== Section Two
== First Title
=== Section One
== Second Title // Error: Multiple top-level headings
.2.2. Block Rules
BLOCK001: Unterminated Blocks
Checks for blocks that are not properly terminated.
Description
This rule ensures that all block delimiters are properly paired. Each opening delimiter must have a matching closing delimiter.
Supported Block Types
-
Listing blocks (----)
-
Example blocks (====)
-
Sidebar blocks (****)
-
Literal blocks (….)
-
Quote blocks (_\_)
-
Table blocks (|===)
-
Comment blocks (////)
-
Passthrough blocks (+\+).
Examples
[source,python]
def hello(): print("Hello, World!")
.Example Block ==== Some example content ====
[source,python]
def hello(): print("Hello, World!")
Some example content
==== BLOCK002: Block Spacing Verifies proper spacing around blocks. ===== Description This rule checks that blocks are properly separated from surrounding content with blank lines, improving readability. ===== Examples .Valid Block Spacing [source,asciidoc]
Some text before the block.
Block content
Some text after the block.
.Invalid Block Spacing [source,asciidoc]
Some text before the block. ---- // Warning: No blank line before block Block content
Some text after the block. // Warning: No blank line after block
=== Whitespace Rules
==== WS001: Whitespace Usage
Ensures proper whitespace usage throughout the document.
===== Checks Performed
-
Consecutive Empty Lines: No more than one consecutive empty line
-
List Marker Spacing: Proper space after list markers (*, -, .)
-
Admonition Block Spacing: Blank line before admonition blocks
-
Trailing Whitespace: No trailing spaces at end of lines
-
Tab Usage: No tabs (use spaces instead)
-
Section Title Spacing: Blank lines around section titles
===== Examples
== Document Title
=== Section Title
* List item 1
* List item 2
NOTE: This is a note.
Some text here.
== Document Title
=== Section Title // Missing blank line before
*Invalid list item // Missing space after marker
NOTE: Invalid note // Missing blank line before
Some text here // Trailing spaces
Tabbed line // Tab instead of spaces
Extra blank line // Too many blank lines
=== Image Rules
==== IMG001: Image Attributes
Verifies image attributes and file references.
===== Description
This rule checks: 1. All images have alt text 2. Referenced local images exist 3. Block images have titles 4. Image attributes are properly formatted
===== Examples
// Inline image with alt text
image:icon.png[Icon]
// Block image with title and alt text
.Figure 1: System Architecture
image::diagram.png[Architecture Diagram]
// External image with alt text
image:https://example.com/img.png[Example Image]
// Missing alt text
image:icon.png[]
// Missing title for block image
image::diagram.png[Diagram]
// Non-existent local file
image::missing.png[Missing Image]
=== Format Rules
==== FMT004: Markdown Syntax Detection
Detects Markdown-style syntax in AsciiDoc files and suggests AsciiDoc alternatives.
===== Description
This rule is particularly useful for documents generated by LLMs that may accidentally use Markdown syntax instead of AsciiDoc. It detects common Markdown patterns and provides suggestions for the correct AsciiDoc equivalent.
===== Detected Patterns
| Markdown Syntax | AsciiDoc Equivalent | Description |
|---|---|---|
|
|
Markdown headings (# through ) |
|
Markdown links |
|
|
|
Markdown images |
```language |
|
Markdown code fences |
|
|
Markdown blockquotes |
===== Examples
# This is a Markdown Heading
Check out [this link](https://example.com) for more info.

> This is a quoted text
```python
print("Hello")
```
== This is an AsciiDoc Heading
Check out link:https://example.com[this link] for more info.
image::images/screenshot.png[Screenshot]
[quote]
____
This is a quoted text
____
[source,python]
print("Hello")
=== Planned Rules
==== TABLE001: Table Formatting (Planned)
Will check table formatting consistency: * Column alignment * Header row presence * Cell content formatting
==== LINK001: Link Verification (Planned)
Will verify: * Internal cross-references * External link validity * Anchor definitions
==== FMT001: Explicit Numbered List Detection
Detects explicit numbered lists (1., 2., etc.) and suggests using AsciiDoc dot-syntax (.).
===== Description
This rule is particularly useful for documents generated by LLMs that may accidentally use explicit numbered lists instead of AsciiDoc’s auto-numbering syntax.
Explicit numbering is problematic because:
-
It doesn’t auto-renumber when items are added/removed
-
It’s not semantic AsciiDoc
-
It creates maintenance burden
===== Examples
1. First step
2. Second step
3. Third step
. First step
. Second step
. Third step
===== Edge Cases
The rule correctly ignores:
-
Decimal numbers like
1.5 kgorVersion 2.0 -
Numbers followed by dot without space:
Chapter 1.Introduction -
Content inside code blocks (----), literal blocks (….), and passthrough blocks ()
==== FMT002: Non-Semantic Definition List Detection
- Detects non-semantic definition list patterns and suggests using AsciiDoc `Term
-
Definition` syntax.
===== Description
This rule is particularly useful for documents generated by LLMs that may create "fake" definition lists using formatting hacks instead of proper AsciiDoc Definition List syntax.
Non-semantic patterns are problematic because:
-
Definition Lists are semantic markup that render properly in all output formats
-
They’re accessible (screen readers understand them)
-
LLM-generated docs often use bold/italic hacks that lose semantics
===== Detected Patterns
| Non-Semantic Pattern | Should Be |
|---|---|
|
|
|
|
|
|
|
|
===== Examples
*API*: Application Programming Interface
**REST**: Representational State Transfer
- *HTTP*: Hypertext Transfer Protocol
API:: Application Programming Interface
REST:: Representational State Transfer
HTTP:: Hypertext Transfer Protocol
===== Edge Cases
The rule correctly ignores:
-
Proper AsciiDoc definition lists (
Term::) -
Inline bold text with colon in sentences
-
AsciiDoc attributes (
:icons: font) -
Content inside code blocks
==== FMT003: Counter Syntax in Title Detection
Detects 1 usage in section titles and suggests reviewing if counters are really needed.
===== Description
This rule is particularly useful for documents generated by LLMs that may use counter syntax in section titles unnecessarily.
Counter syntax in titles is often problematic because:
-
Simple sequential sections don’t need explicit counters
-
Counter state can be confusing across includes
-
LLMs often mimic numbered headings from other formats unnecessarily
===== Examples
=== Phase {counter:phase}
==== Step {counter:step}: Configuration
=== {counter:section} Overview
=== Phase 1: Discovery
=== Phase 2: Implementation
// Or use descriptive titles:
=== Discovery Phase
=== Implementation Phase
===== Edge Cases
The rule correctly ignores:
-
Counter syntax in regular content (not in section titles)
-
Counter with format specifiers (
%02d) -
variant is also detected -
Content inside code blocks
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.