DEV Community

Kazu
Kazu

Posted on

Inside gomarklint: Building a High-Performance Markdown Linter in Go

GitHub logo shinagawa-web / gomarklint

A fast and configurable Markdown linter written in Go

gomarklint

Test codecov Go Report Card Go Reference License: MIT

English | 日本語

A fast, opinionated Markdown linter for engineering teams. Built in Go, designed for CI.

Download binary (no Go required):

Download the latest binary for your platform from GitHub Releases.

# macOS / Linux
tar -xzf gomarklint_Darwin_x86_64.tar.gz
sudo mv gomarklint /usr/local/bin/
# or install to user-local directory (no sudo required)
mkdir -p ~/.local/bin && mv gomarklint ~/.local/bin/
Enter fullscreen mode Exit fullscreen mode
# Windows (PowerShell)
Expand-Archive -Path gomarklint_Windows_x86_64.zip -DestinationPath "$env:LOCALAPPDATA\Programs\gomarklint"
# Add to PATH (run once)
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";$env:LOCALAPPDATA\Programs\gomarklint", "User")
Enter fullscreen mode Exit fullscreen mode

Via go install:

go install github.com/shinagawa-web/gomarklint@latest
Enter fullscreen mode Exit fullscreen mode
  • Catch broken links and headings before your docs ship.
  • Enforce predictable structure (no more "why is this H4 under H2?").
  • Output that's friendly for both humans and machines (JSON).
  • Process 100,000+ lines in ~170ms — fast…

Intro

Most Markdown linters focus on correctness, but often at the expense of speed, flexibility, or ease of integration. When you’re working in a large repository with thousands of Markdown files, even a small slowdown in linting can make every CI run feel sluggish.

gomarklint is my answer to that problem — a fast, minimal, CI-friendly Markdown linter written in Go. It checks for common issues like inconsistent heading levels, duplicate headings, unclosed code blocks, and optional external link validation. It’s designed to scan tens of thousands of lines in under 50ms, while staying lightweight enough to configure in seconds.

In this article, we’ll go under the hood of gomarklint: how it’s structured, how it parses Markdown efficiently, how the rules engine works, and which optimizations make it fast. But before we dive into the internals, let’s take a quick look at how to use it.

Quick Start — Using gomarklint

Install

You can install via Go:

go install github.com/shinagawa-web/gomarklint@latest
Enter fullscreen mode Exit fullscreen mode

Initialize Config

This creates a .gomarklint.json file with default rules and file patterns.

gomarklint init
Enter fullscreen mode Exit fullscreen mode

Run Linting

By default, it checks for heading consistency, duplicate headings, unclosed code blocks, and missing trailing blank lines.

gomarklint ./docs
Enter fullscreen mode Exit fullscreen mode

Optional — GitHub Actions Integration

Use the official GitHub Action to run linting automatically on every PR.

Overview of the Architecture

gomarklint’s structure is intentionally simple. The cmd/ package contains the CLI entry point, built with Cobra for easy flag handling. Core logic lives in internal/, which is split into rule/ for linting rules and parser/ for file handling. This separation makes it easy to add new rules without touching CLI code.

Parsing Markdown Files Efficiently

The parser.ExpandPaths() function recursively finds .md files while ignoring hidden directories and symlinks. It also applies include and ignore patterns from the config file. YAML frontmatter is detected and skipped so that headings inside it don’t trigger false positives.

Rule Engine Design

Each rule is a self-contained function that returns a list of LintError structs, each containing the file name, line number, and message. Errors are sorted by line number before output. This keeps the codebase predictable and easy to extend.

Output & CI Integration

gomarklint supports both text and JSON output. JSON mode is especially useful for CI pipelines, where structured output can be parsed by other tools. The program exits with a non-zero status if any errors are found, but only when running in CI (GITHUB_ACTIONS=true), so local runs don’t break your workflow unnecessarily.

Performance Considerations

Because performance was a priority from the start, file reading and rule checks are optimized to minimize allocations. In most cases, link checking is the only operation that significantly impacts runtime, and it’s disabled by default for speed.

Try It on Your Project

If you want to keep your Markdown clean and consistent without slowing down your workflow, try gomarklint today:

Repository:

GitHub logo shinagawa-web / gomarklint

A fast and configurable Markdown linter written in Go

gomarklint

Test codecov Go Report Card Go Reference License: MIT

English | 日本語

A fast, opinionated Markdown linter for engineering teams. Built in Go, designed for CI.

Download binary (no Go required):

Download the latest binary for your platform from GitHub Releases.

# macOS / Linux
tar -xzf gomarklint_Darwin_x86_64.tar.gz
sudo mv gomarklint /usr/local/bin/
# or install to user-local directory (no sudo required)
mkdir -p ~/.local/bin && mv gomarklint ~/.local/bin/
Enter fullscreen mode Exit fullscreen mode
# Windows (PowerShell)
Expand-Archive -Path gomarklint_Windows_x86_64.zip -DestinationPath "$env:LOCALAPPDATA\Programs\gomarklint"
# Add to PATH (run once)
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";$env:LOCALAPPDATA\Programs\gomarklint", "User")
Enter fullscreen mode Exit fullscreen mode

Via go install:

go install github.com/shinagawa-web/gomarklint@latest
Enter fullscreen mode Exit fullscreen mode
  • Catch broken links and headings before your docs ship.
  • Enforce predictable structure (no more "why is this H4 under H2?").
  • Output that's friendly for both humans and machines (JSON).
  • Process 100,000+ lines in ~170ms — fast…

GitHub Action:

https://github.com/marketplace/actions/gomarklint-markdown-linter

Clone the repo, install it, and run gomarklint init — you’ll be linting your docs in under a minute.

Top comments (0)