Update MD060/table-column-style to account for emoji and CJK characters rendering at 2x Latin width (fixes #1869).

This commit is contained in:
David Anson 2025-11-25 14:15:50 -08:00
parent ec380a87c0
commit 9b116ae889
13 changed files with 254 additions and 61 deletions

View file

@ -2,10 +2,37 @@
import { filterByTypes } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs";
import stringWidth from "string-width";
/** @typedef {import("micromark-extension-gfm-table")} */
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @typedef {import("markdownlint").RuleOnErrorInfo} RuleOnErrorInfo */
/**
* @typedef Column
* @property {number} actual Actual column (1-based).
* @property {number} effective Effective column (1-based).
*/
/**
* Gets a list of table cell divider columns.
*
* @param {readonly string[]} lines File/string lines.
* @param {MicromarkToken} row Micromark row token.
* @returns {Column[]} Divider columns.
*/
function getTableDividerColumns(lines, row) {
return filterByTypes(
row.children,
[ "tableCellDivider" ]
).map(
(divider) => ({
"actual": divider.startColumn,
"effective": stringWidth(lines[row.startLine - 1].slice(0, divider.startColumn - 1))
})
);
}
/**
* Adds a RuleOnErrorInfo object to a list of RuleOnErrorInfo objects.
*
@ -44,13 +71,13 @@ export default {
/** @type {RuleOnErrorInfo[]} */
const errorsIfAligned = [];
if (styleAlignedAllowed) {
const headingDividerColumns = filterByTypes(headingRow.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn);
const headingDividerColumns = getTableDividerColumns(params.lines, headingRow);
for (const row of rows.slice(1)) {
const remainingHeadingDividerColumns = new Set(headingDividerColumns);
const rowDividerColumns = filterByTypes(row.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn);
const remainingHeadingDividerColumns = new Set(headingDividerColumns.map((column) => column.effective));
const rowDividerColumns = getTableDividerColumns(params.lines, row);
for (const dividerColumn of rowDividerColumns) {
if ((remainingHeadingDividerColumns.size > 0) && !remainingHeadingDividerColumns.delete(dividerColumn)) {
addError(errorsIfAligned, row.startLine, dividerColumn, "Table pipe does not align with heading for style \"aligned\"");
if ((remainingHeadingDividerColumns.size > 0) && !remainingHeadingDividerColumns.delete(dividerColumn.effective)) {
addError(errorsIfAligned, row.startLine, dividerColumn.actual, "Table pipe does not align with heading for style \"aligned\"");
}
}
}