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

Table 1. Technology Stack
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.

# 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 .
Basic Usage
# Check a single file
asciidoc-lint document.adoc

# Check multiple files
asciidoc-lint doc1.adoc doc2.adoc

# Get help
asciidoc-lint --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
  1. 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
  2. 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")
  3. Register the rule in the linter

  4. 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

  1. Create feature branch

  2. Implement changes

  3. Add/update tests

  4. Update documentation

  5. Run full test suite

  6. Submit PR

Release Process

  1. Update version number in init.py

  2. Update changelog

  3. Run full test suite

  4. Create release notes

  5. Tag release

  6. Build and publish

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

.1. Usage Guide

.1.1. Command Line Interface

Basic Usage
# Check a single file
asciidoc-lint document.adoc

# Check multiple files
asciidoc-lint doc1.adoc doc2.adoc

# Check with specific output format
asciidoc-lint --format json document.adoc

# Use specific configuration
asciidoc-lint --config my-config.yml 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

--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
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-lint $files
fi
CI/CD Integration
GitHub Actions Example
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-lint 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
Table 2. Common Issues and Solutions
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-lint --debug document.adoc

# Show rule processing details
asciidoc-lint --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
Valid Heading Hierarchy
== Document Title (Level 1)

=== Section (Level 2)

==== Subsection (Level 3)

=== Another Section (Level 2)
Invalid Heading Hierarchy
== 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
Valid Heading Format
== Document Title
=== Section Title
==== Subsection Title
Invalid Heading Format
=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
Valid Single Top Level
== Main Document Title

=== Section One
=== Section Two
Invalid Multiple Top Level
== 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
Valid Block Termination
[source,python]

def hello(): print("Hello, World!")

.Example Block
====
Some example content
====
Invalid Block Termination
[source,python]

def hello(): print("Hello, World!")

Example 1. Example Block

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

  1. Consecutive Empty Lines: No more than one consecutive empty line

  2. List Marker Spacing: Proper space after list markers (*, -, .)

  3. Admonition Block Spacing: Blank line before admonition blocks

  4. Trailing Whitespace: No trailing spaces at end of lines

  5. Tab Usage: No tabs (use spaces instead)

  6. Section Title Spacing: Blank lines around section titles

===== Examples

Valid Whitespace Usage
== Document Title

=== Section Title

* List item 1
* List item 2

NOTE: This is a note.

Some text here.
Invalid Whitespace Usage
== 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

Valid Image Usage
// 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]
Invalid Image Usage
// Missing alt text
image:icon.png[]

// Missing title for block image
image::diagram.png[Diagram]

// Non-existent local file
image::missing.png[Missing Image]

=== 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: Format Consistency (Planned)

Will check: * Consistent emphasis style * List formatting * Admonition usage