platform-tooling/markdownlint-rules/file-length.cjs
Quinn Ftw 93d2620b37 chore(markdownlint-rules): 🔧 Update Markdown linting rules for stricter formatting validation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-03-02 21:06:54 -08:00

94 lines
2.8 KiB
JavaScript

/**
* markdownlint custom rule: file-length
*
* Enforces maximum word count for markdown documents with dual-threshold
* support, mirroring @lilith/eslint-plugin-file-length for TypeScript.
*
* Philosophy: Long markdown documents should be decomposed into focused,
* self-contained pieces — the same SRP principle applied to prose.
*
* Supports inline directives to exclude sections (e.g. large tables) from
* the word count. Uses custom comments read directly from disk, because
* markdownlint redacts HTML comment content in params.lines.
*
* <!-- wordcount:off -->
* | ... large table ... |
* <!-- wordcount:on -->
*
* Directive lines themselves are never counted.
*
* Configuration (in .markdownlint-cli2.jsonc under "config"):
* "file-length": {
* "warnThreshold": 2500, // default: 2500 words
* "errorThreshold": 3000 // default: 3000 words
* }
*/
'use strict';
const fs = require('fs');
const DEFAULT_WARN_THRESHOLD = 2500;
const DEFAULT_ERROR_THRESHOLD = 3000;
const WORDCOUNT_OFF = /^\s*<!--\s*wordcount:off\s*-->\s*$/;
const WORDCOUNT_ON = /^\s*<!--\s*wordcount:on\s*-->\s*$/;
function countWords(line) {
const tokens = line.trim().split(/\s+/).filter(Boolean);
return tokens.length;
}
module.exports = {
names: ['file-length', 'loc'],
description: 'Enforce maximum word count for markdown documents',
tags: ['document-size'],
parser: 'none',
function: function rule(params, onError) {
const config = params.config || {};
const warnThreshold = config.warnThreshold || DEFAULT_WARN_THRESHOLD;
const errorThreshold = config.errorThreshold || DEFAULT_ERROR_THRESHOLD;
// Read raw file from disk — markdownlint redacts HTML comment content
// in params.lines, making directive parsing impossible otherwise.
let rawLines;
try {
rawLines = fs.readFileSync(params.name, 'utf8').split('\n');
} catch {
rawLines = params.lines;
}
let counting = true;
let wordCount = 0;
for (const line of rawLines) {
if (WORDCOUNT_OFF.test(line)) {
counting = false;
continue;
}
if (WORDCOUNT_ON.test(line)) {
counting = true;
continue;
}
if (counting) {
wordCount += countWords(line);
}
}
if (wordCount > errorThreshold) {
onError({
lineNumber: 1,
detail: `File has ${wordCount} words (max ${errorThreshold}). Split into smaller, focused documents.`,
context: `${wordCount}/${errorThreshold} words`,
});
} else if (wordCount > warnThreshold) {
onError({
lineNumber: 1,
detail: `File has ${wordCount} words (warning at ${warnThreshold}). Consider splitting before it reaches ${errorThreshold} words.`,
context: `${wordCount}/${warnThreshold} words`,
});
}
},
};