markdownlint-custom-rules
Create custom linting rules for markdownlint including rule structure, parser integration, error reporting, and automatic fixing.
What this skill does
# Markdownlint Custom Rules
Master creating custom markdownlint rules including rule structure, markdown-it and micromark parser integration, error reporting with fixInfo, and asynchronous rule development.
## Overview
Markdownlint allows you to create custom rules tailored to your project's specific documentation requirements. Custom rules can enforce project-specific conventions, validate content patterns, and ensure consistency beyond what built-in rules provide.
## Rule Object Structure
### Basic Rule Definition
Every custom rule must be a JavaScript object with specific properties:
```javascript
module.exports = {
names: ["rule-name", "RULE001"],
description: "Description of what this rule checks",
tags: ["custom", "style"],
parser: "markdownit",
function: function(params, onError) {
// Rule implementation
}
};
```
### Required Properties
```javascript
{
names: Array<String>, // Rule identifiers (required)
description: String, // What the rule checks (required)
tags: Array<String>, // Categorization tags (required)
parser: String, // "markdownit", "micromark", or "none" (required)
function: Function // Rule logic (required)
}
```
### Optional Properties
```javascript
{
information: URL, // Link to rule documentation
asynchronous: Boolean // If true, function returns Promise
}
```
## Parser Selection
### markdown-it Parser
Best for token-based parsing with rich metadata:
```javascript
module.exports = {
names: ["any-blockquote-markdown-it"],
description: "Rule that reports an error for any blockquote",
information: new URL("https://example.com/rules/any-blockquote"),
tags: ["test"],
parser: "markdownit",
function: (params, onError) => {
const blockquotes = params.parsers.markdownit.tokens
.filter((token) => token.type === "blockquote_open");
for (const blockquote of blockquotes) {
const [startIndex, endIndex] = blockquote.map;
const lines = endIndex - startIndex;
onError({
lineNumber: blockquote.lineNumber,
detail: `Blockquote spans ${lines} line(s).`,
context: blockquote.line
});
}
}
};
```
### micromark Parser
Best for detailed token analysis and precise positioning:
```javascript
module.exports = {
names: ["any-blockquote-micromark"],
description: "Rule that reports an error for any blockquote",
information: new URL("https://example.com/rules/any-blockquote"),
tags: ["test"],
parser: "micromark",
function: (params, onError) => {
const blockquotes = params.parsers.micromark.tokens
.filter((token) => token.type === "blockQuote");
for (const blockquote of blockquotes) {
const lines = blockquote.endLine - blockquote.startLine + 1;
onError({
lineNumber: blockquote.startLine,
detail: `Blockquote spans ${lines} line(s).`,
context: params.lines[blockquote.startLine - 1]
});
}
}
};
```
### No Parser
For simple line-based rules:
```javascript
module.exports = {
names: ["no-todo-comments"],
description: "Disallow TODO comments in markdown",
tags: ["custom"],
parser: "none",
function: (params, onError) => {
params.lines.forEach((line, index) => {
if (line.includes("TODO:") || line.includes("FIXME:")) {
onError({
lineNumber: index + 1,
detail: "TODO/FIXME comments should be resolved",
context: line.trim()
});
}
});
}
};
```
## Function Parameters
### params Object
The `params` object contains all information about the markdown content:
```javascript
function rule(params, onError) {
// params.name - Input file/string name
// params.lines - Array of lines (string[])
// params.frontMatterLines - Lines of front matter
// params.config - Rule's configuration from .markdownlint.json
// params.version - markdownlint library version
// params.parsers - Parser outputs
}
```
### Accessing Lines
```javascript
function: (params, onError) => {
params.lines.forEach((line, index) => {
const lineNumber = index + 1; // Lines are 1-based
if (someCondition(line)) {
onError({
lineNumber,
detail: "Issue description",
context: line.trim()
});
}
});
}
```
### Using Configuration
```javascript
// In .markdownlint.json
{
"custom-rule": {
"max_length": 50,
"pattern": "^[A-Z]"
}
}
// In rule
function: (params, onError) => {
const config = params.config || {};
const maxLength = config.max_length || 40;
const pattern = config.pattern ? new RegExp(config.pattern) : null;
// Use configuration values
}
```
### Working with Front Matter
```javascript
function: (params, onError) => {
const frontMatterLines = params.frontMatterLines;
if (frontMatterLines.length > 0) {
// Process YAML front matter
const frontMatter = frontMatterLines.join('\n');
// Validate front matter
}
}
```
## Error Reporting with onError
### Basic Error Reporting
```javascript
onError({
lineNumber: 5, // Required: 1-based line number
detail: "Line exceeds maximum length", // Optional: Additional info
context: "This is the problematic..." // Optional: Relevant text
});
```
### Error with Range
Highlight specific portion of the line:
```javascript
onError({
lineNumber: 10,
detail: "Invalid heading format",
context: "### Heading",
range: [1, 3] // Column 1, length 3 (highlights "###")
});
```
### Error with Fix Information
Enable automatic fixing:
```javascript
onError({
lineNumber: 15,
detail: "Extra whitespace",
context: " text ",
fixInfo: {
editColumn: 1,
deleteCount: 2,
insertText: ""
}
});
```
## Automatic Fixing with fixInfo
### Delete Characters
```javascript
// Remove 5 characters starting at column 10
fixInfo: {
lineNumber: 5,
editColumn: 10,
deleteCount: 5
}
```
### Insert Text
```javascript
// Insert text at column 1
fixInfo: {
lineNumber: 3,
editColumn: 1,
insertText: "# "
}
```
### Replace Text
```javascript
// Replace 3 characters with new text
fixInfo: {
lineNumber: 7,
editColumn: 5,
deleteCount: 3,
insertText: "new"
}
```
### Delete Entire Line
```javascript
// Delete the entire line
fixInfo: {
lineNumber: 10,
deleteCount: -1
}
```
### Insert New Line
```javascript
// Insert a blank line
fixInfo: {
lineNumber: 8,
insertText: "\n"
}
```
### Multi-Line Fix
Report multiple fixes for the same violation:
```javascript
function: (params, onError) => {
// Fix requires changes on multiple lines
onError({
lineNumber: 5,
detail: "Inconsistent list markers",
fixInfo: {
lineNumber: 5,
editColumn: 1,
deleteCount: 1,
insertText: "-"
}
});
onError({
lineNumber: 6,
detail: "Inconsistent list markers",
fixInfo: {
lineNumber: 6,
editColumn: 1,
deleteCount: 1,
insertText: "-"
}
});
}
```
## Complete Rule Examples
### Enforce Heading Capitalization
```javascript
module.exports = {
names: ["heading-capitalization", "HC001"],
description: "Headings must start with a capital letter",
tags: ["headings", "custom"],
parser: "markdownit",
function: (params, onError) => {
const headings = params.parsers.markdownit.tokens
.filter(token => token.type === "heading_open");
for (const heading of headings) {
const headingLine = params.lines[heading.lineNumber - 1];
const match = headingLine.match(/^#+\s+(.+)$/);
if (match) {
const text = match[1];
const firstChar = text.charAt(0);
if (firstChar !== firstChar.toUpperCase()) {
const hashCount = headingLine.indexOf(' ');
onError({
lineNumber: heading.lineNumber,
detail: "Heading must start with capital letter",
context: headingLine,
range: [hashCount + 2, 1],
fixInfo: {
Related in Data & Analytics
clawarr-suite
IncludedComprehensive management for self-hosted media stacks (Sonarr, Radarr, Lidarr, Readarr, Prowlarr, Bazarr, Overseerr, Plex, Tautulli, SABnzbd, Recyclarr, Unpackerr, Notifiarr, Maintainerr, Kometa, FlareSolverr). Deep library exploration, analytics, dashboard generation, content management, request handling, subtitle management, indexer control, download monitoring, quality profile sync, library cleanup automation, notification routing, collection/overlay management, and media tracker integration (Trakt, Letterboxd, Simkl).
querying-soql
IncludedSOQL query generation, optimization, and analysis with 100-point scoring. Use this skill when the user needs SOQL/SOSL authoring or optimization: natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance or safety improvements for Salesforce queries. TRIGGER when: user writes, optimizes, or debugs SOQL/SOSL queries, touches .soql files, or asks about relationship queries, aggregates, or query performance. DO NOT TRIGGER when: bulk data operations (use handling-sf-data), Apex DML logic (use generating-apex), or report/dashboard queries.
app-store-optimization
IncludedApp Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklists, and tracking ranking changes.
habit-flow
IncludedAI-powered atomic habit tracker with natural language logging, streak tracking, smart reminders, and coaching. Use for creating habits, logging completions naturally ("I meditated today"), viewing progress, and getting personalized coaching.
app-store-optimization
IncludedApp Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklists, and tracking ranking changes.
visualizing-data
IncludedBuilds dashboards, reports, and data-driven interfaces requiring charts, graphs, or visual analytics. Provides systematic framework for selecting appropriate visualizations based on data characteristics and analytical purpose. Includes 24+ visualization types organized by purpose (trends, comparisons, distributions, relationships, flows, hierarchies, geospatial), accessibility patterns (WCAG 2.1 AA compliance), colorblind-safe palettes, and performance optimization strategies. Use when creating visualizations, choosing chart types, displaying data graphically, or designing data interfaces.