94 lines
2.8 KiB
JavaScript
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`,
|
|
});
|
|
}
|
|
},
|
|
};
|