markdownlint
Version:
A Node.js style checker and lint tool for Markdown/CommonMark files.
97 lines (92 loc) • 3.52 kB
JavaScript
// @ts-check
import { addErrorDetailIf } from "../helpers/helpers.cjs";
import { getDescendantsByType } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs";
const listStyleExamples = {
"one": "1/1/1",
"ordered": "1/2/3",
"zero": "0/0/0"
};
const listStyles = Object.keys(listStyleExamples);
/**
* Gets the column and text of an ordered list item prefix token.
*
* @param {import("markdownlint").MicromarkToken} listItemPrefix List item prefix token.
* @returns {{startColumn: number, endColumn: number, text: string, number: number}} List item value column and text.
*/
function getOrderedListItemValue(listItemPrefix) {
const listItemValue = getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0];
const { startColumn, endColumn, text } = listItemValue;
return {
startColumn,
endColumn,
text,
"number": Number(text)
};
}
/** @type {import("markdownlint").Rule} */
export default {
"names": [ "MD029", "ol-prefix" ],
"description": "Ordered list item prefix",
"tags": [ "ol" ],
"parser": "micromark",
"function": function MD029(params, onError) {
const style = String(params.config.style);
for (const listOrdered of filterByTypesCached([ "listOrdered" ])) {
const listItemPrefixes = getDescendantsByType(listOrdered, [ "listItemPrefix" ]);
let expectedNumber = 1;
let incrementing = false;
let rightAligned = false;
let zeroPrefixed = false;
if (listItemPrefixes.length > 0) {
// Check for right alignment
const firstPrefixEndColumn = listItemPrefixes[0].endColumn;
rightAligned = listItemPrefixes.every((prefix) => prefix.endColumn === firstPrefixEndColumn);
if (listItemPrefixes.length > 1) {
// Check for incrementing number pattern 1/2/3 or 0/1/2
const firstValue = getOrderedListItemValue(listItemPrefixes[0]);
const secondValue = getOrderedListItemValue(listItemPrefixes[1]);
if ((secondValue.number !== 1) || (firstValue.number === 0)) {
incrementing = true;
if (firstValue.number === 0) {
expectedNumber = 0;
}
}
zeroPrefixed = firstValue.text.startsWith("0");
}
}
// Determine effective style
const listStyle = listStyles.includes(style) ? style : (incrementing ? "ordered" : "one");
if (listStyle === "zero") {
expectedNumber = 0;
} else if (listStyle === "one") {
expectedNumber = 1;
}
// Validate each list item marker
for (const listItemPrefix of listItemPrefixes) {
const expectedText = expectedNumber.toString();
const { endColumn, startColumn, "number": actualNumber } = getOrderedListItemValue(listItemPrefix);
const length = endColumn - startColumn;
const fixInfo = {
"editColumn": startColumn,
"deleteCount": length,
"insertText": rightAligned ? expectedText.padStart(length, (zeroPrefixed ? "0" : " ")) : expectedText
};
addErrorDetailIf(
onError,
listItemPrefix.startLine,
expectedNumber,
actualNumber,
// @ts-ignore
"Style: " + listStyleExamples[listStyle],
undefined,
[ listItemPrefix.startColumn, listItemPrefix.endColumn - listItemPrefix.startColumn ],
fixInfo
);
if (listStyle === "ordered") {
expectedNumber++;
}
}
}
}
};