Migrating from Other Markdown Linters#

This guide helps teams switching to gomarklint from markdownlint, remark-lint, or textlint. It covers installation replacement, rule mapping, configuration conversion, and CI migration.

Why switch?

  • Single static binary — no Node.js or Python runtime required
  • Fast concurrent linting via goroutines
  • First-class CI integration with a native GitHub Action
  • Frontmatter-aware parsing (YAML/TOML stripped before linting)
  • Live HTTP link validation built in (external-link rule)

Installation#

Remove your existing linter and install gomarklint:

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/shinagawa-web/gomarklint/main/install.sh | sh

# Homebrew
brew install shinagawa-web/tap/gomarklint

# npm
npm install -g @shinagawa-web/gomarklint

# go install
go install github.com/shinagawa-web/gomarklint/v3@latest

Remove the old linter from your project:

# if you used markdownlint-cli2:
npm uninstall markdownlint-cli2

# if you used remark-lint:
npm uninstall remark remark-cli remark-lint remark-preset-lint-recommended

# if you used textlint:
npm uninstall textlint textlint-rule-*

If you used npm only to run the linter, you may be able to remove package.json entirely and replace the script entry with the gomarklint binary.


markdownlint#

markdownlint rule mapping#

markdownlint rulegomarklint ruleNotes
MD001 heading-incrementheading-levelChecks heading level progression
MD003 heading-styleno-setext-headingsATX enforcement only; setext detection is the default check
MD004 ul-styleconsistent-list-markerOptions: consistent | dash | asterisk | plus
MD009 no-trailing-spaces—Not yet implemented
MD010 no-hard-tabsno-hard-tabs—
MD012 no-multiple-blanksno-multiple-blank-lines—
MD013 line-lengthmax-line-lengthDefault off; set lineLength option
MD022 blanks-around-headingsblanks-around-headings—
MD024 no-duplicate-headingduplicate-heading—
MD025 single-h1single-h1—
MD026 no-trailing-punctuationno-trailing-punctuationpunctuation option configures the character set
MD031 blanks-around-fencesblanks-around-fences—
MD032 blanks-around-listsblanks-around-lists—
MD034 no-bare-urlsno-bare-urls—
MD036 no-emphasis-as-headingno-emphasis-as-headingPunctuation-ending spans are excluded
MD040 fenced-code-languagefenced-code-language—
MD041 first-line-heading—No direct equivalent — heading-level checks level progression but does not require a heading on the first line
MD042 no-empty-linksno-empty-linksAlso catches [](#) and [](<>)
MD045 no-alt-textempty-alt-text—
MD047 single-trailing-newlinefinal-blank-line—
MD048 code-fence-styleconsistent-code-fenceOptions: consistent | backtick | tilde
MD049 emphasis-styleconsistent-emphasis-styleOptions: consistent | asterisk | underscore
MD051 link-fragmentslink-fragmentsConfigurable slug algorithm; default off
MD052 reference-links-images—Not yet implemented
MD053 link-image-style—Not yet implemented

Rules without a gomarklint equivalent yet: MD005–MD008 (list indent), MD011, MD014, MD018–MD021 (heading spaces), MD023, MD027–MD030, MD033, MD035, MD037–MD039, MD043, MD044, MD046, MD050, MD054–MD056, MD058, MD059.

markdownlint config conversion#

Before (.markdownlint.json / .markdownlint-cli2.jsonc):

{
  "default": true,
  "MD013": { "line_length": 120 },
  "MD024": false,
  "MD026": { "punctuation": ".,;:!?" }
}

After (.gomarklint.json):

{
  "default": true,
  "rules": {
    "max-line-length": { "enabled": true, "lineLength": 120 },
    "duplicate-heading": false,
    "no-trailing-punctuation": { "punctuation": ".,;:!?" }
  }
}

Key differences:

  • Rule keys are descriptive names (no-trailing-punctuation) rather than MD numbers (MD026).
  • All rule configuration lives under rules.<rule-key>.
  • false disables a rule; true enables it with defaults; an object enables it with options.

markdownlint CI migration#

Before (GitHub Actions with markdownlint-cli2):

- uses: DavidAnson/markdownlint-cli2-action@v18
  with:
    globs: "**/*.md"

After (gomarklint):

- uses: actions/setup-go@v5
  with:
    go-version-file: 'go.mod'

- uses: shinagawa-web/gomarklint-action@v1

remark-lint#

remark-lint rule mapping#

remark-lint rulegomarklint ruleNotes
final-newlinefinal-blank-line—
no-consecutive-blank-linesno-multiple-blank-lines—
heading-incrementheading-level—
first-heading-level—No direct equivalent — heading-level checks level progression but does not enforce position
no-duplicate-headingsduplicate-heading—
heading-style (atx)no-setext-headings—
no-missing-blank-linesblanks-around-headings, blanks-around-lists, blanks-around-fencesremark-lint combines these; gomarklint has separate rules
no-literal-urlsno-bare-urls—
no-empty-urlno-empty-links—
no-emphasis-as-headingno-emphasis-as-heading—
fenced-code-flagfenced-code-language—
no-multiple-toplevel-headingssingle-h1—
maximum-line-lengthmax-line-lengthDefault off
no-tabsno-hard-tabs—
no-heading-punctuationno-trailing-punctuation—
fenced-code-markerconsistent-code-fence—
emphasis-markerconsistent-emphasis-style—
unordered-list-marker-styleconsistent-list-marker—
no-undefined-references—Not yet implemented (Priority 3)
hard-break-spaces—no-trailing-spaces not yet implemented
linebreak-style—consistent-line-endings not yet implemented

remark-lint config conversion#

Before (.remarkrc.mjs):

import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import remarkLintMaximumLineLength from 'remark-lint-maximum-line-length'
import remarkLintEmphasisMarker from 'remark-lint-emphasis-marker'

export default {
  plugins: [
    remarkPresetLintRecommended,
    [remarkLintMaximumLineLength, 120],
    [remarkLintEmphasisMarker, '*'],
  ],
}

After (.gomarklint.json):

{
  "default": true,
  "rules": {
    "max-line-length": { "enabled": true, "lineLength": 120 },
    "consistent-emphasis-style": { "style": "asterisk" }
  }
}

remark-lint CI migration#

Before (GitHub Actions with remark-cli):

- name: Install remark
  run: npm install -g remark-cli remark-preset-lint-recommended

- name: Lint Markdown
  run: remark --use remark-preset-lint-recommended .

After:

- uses: actions/setup-go@v5
  with:
    go-version-file: 'go.mod'

- uses: shinagawa-web/gomarklint-action@v1

textlint#

textlint focuses primarily on prose style (word choice, grammar, writing conventions). gomarklint focuses on Markdown structure. The overlap is narrow — only structural rules have equivalents.

textlint rule mapping#

textlint rule / plugingomarklint ruleNotes
@textlint-rule/no-duplicate-headingduplicate-heading—
textlint-rule-no-empty-elementno-empty-links, empty-alt-textPartial overlap
textlint-rule-no-todo—No equivalent
textlint-rule-ja-*—Japanese prose rules; no equivalent
textlint-rule-spellcheck-tech-word—No equivalent

Most textlint rules (terminology, spacing, punctuation style for prose) have no structural equivalent in gomarklint. If you use textlint for writing quality checks, you can run both tools side by side during a transition period.

Running both tools in parallel#

{
  "scripts": {
    "lint:structure": "gomarklint .",
    "lint:prose": "textlint **/*.md",
    "lint": "npm run lint:structure && npm run lint:prose"
  }
}

Handling uncovered rules#

Rules not yet implemented in gomarklint (from the roadmap):

Planned rulemarkdownlint equivalentremark-lint equivalentPriority
no-trailing-spacesMD009hard-break-spacesPriority 3
no-undefined-referencesMD052/MD053no-undefined-referencesPriority 3
consistent-line-endings—linebreak-stylePriority 3
table-formattingMD055/MD056/MD058table-pipes, table-cell-paddingPriority 3
descriptive-link-textMD059—Priority 3

To request new rules or track progress, see issue #76.


FAQ#

Can I run gomarklint alongside markdownlint during migration?

Yes. Both tools can run independently on the same files. Use severity warning in gomarklint while you fix violations:

{
  "default": true,
  "rules": {
    "no-bare-urls": { "severity": "warning" }
  }
}

How do I gradually adopt gomarklint?

Start with default: false and enable only the rules you want to enforce first. Add more rules incrementally as your team addresses violations:

{
  "default": false,
  "rules": {
    "final-blank-line": true,
    "fenced-code-language": true
  }
}

Does gomarklint support inline disable comments?

Yes. Use <!-- gomarklint-disable --> and <!-- gomarklint-enable --> to suppress violations for a block, or <!-- gomarklint-disable rule-name --> to disable a specific rule. See the disable comments page.

Does gomarklint support per-directory config files?

Not automatically. markdownlint-cli2 traverses directories and applies the nearest .markdownlint.json it finds — gomarklint does not do this yet. The workaround is to exclude the subdirectory from the root config and run gomarklint a second time with an explicit --config:

{
  "ignore": ["docs/"]
}
gomarklint .                                   # root config
gomarklint docs/ --config docs/.gomarklint.json  # docs-specific config

Does gomarklint support shared configs across repositories?

Not yet. There is no extends mechanism to inherit from an npm package or a remote config file. Each repository maintains its own .gomarklint.json.