wip
Some checks are pending
Checkers / linkcheck (push) Waiting to run
Checkers / spellcheck (push) Waiting to run
CI / build (20, macos-latest) (push) Waiting to run
CI / build (20, ubuntu-latest) (push) Waiting to run
CI / build (20, windows-latest) (push) Waiting to run
CI / build (22, macos-latest) (push) Waiting to run
CI / build (22, ubuntu-latest) (push) Waiting to run
CI / build (22, windows-latest) (push) Waiting to run
CI / build (24, macos-latest) (push) Waiting to run
CI / build (24, ubuntu-latest) (push) Waiting to run
CI / build (24, windows-latest) (push) Waiting to run
CI / pnpm (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
TestRepos / build (latest, ubuntu-latest) (push) Waiting to run
UpdateTestRepos / update (push) Waiting to run

This commit is contained in:
David Anson 2025-11-23 17:36:21 -08:00
parent 75bb84620e
commit 4447540366
13 changed files with 196 additions and 24 deletions

View file

@ -2697,7 +2697,7 @@ Parameters:
- `style`: Table column style (`string`, default `any`, values `aligned` / - `style`: Table column style (`string`, default `any`, values `aligned` /
`any` / `compact` / `tight`) `any` / `compact` / `tight`)
- `wide_character`: RegExp for matching wide character(s) (`string`, default - `wide_character`: RegExp for matching wide character(s) (`string`, default
`undefined`) `TBD`)
This rule is triggered when the column separators of a This rule is triggered when the column separators of a
[GitHub Flavored Markdown table][gfm-table-060] are used inconsistently. [GitHub Flavored Markdown table][gfm-table-060] are used inconsistently.

View file

@ -9,7 +9,7 @@ Parameters:
- `style`: Table column style (`string`, default `any`, values `aligned` / - `style`: Table column style (`string`, default `any`, values `aligned` /
`any` / `compact` / `tight`) `any` / `compact` / `tight`)
- `wide_character`: RegExp for matching wide character(s) (`string`, default - `wide_character`: RegExp for matching wide character(s) (`string`, default
`undefined`) `TBD`)
This rule is triggered when the column separators of a This rule is triggered when the column separators of a
[GitHub Flavored Markdown table][gfm-table-060] are used inconsistently. [GitHub Flavored Markdown table][gfm-table-060] are used inconsistently.

View file

@ -7,8 +7,19 @@ import { filterByTypesCached } from "./cache.mjs";
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @typedef {import("markdownlint").RuleOnErrorInfo} RuleOnErrorInfo */ /** @typedef {import("markdownlint").RuleOnErrorInfo} RuleOnErrorInfo */
// See https://unicode.org/reports/tr51/ const regExpFlags = "gv";
const defaultWideCharacterReString = "\\p{RGI_Emoji}"; const anyCharacterRe = new RegExp("[\\s\\S]", regExpFlags);
// See:
// https://www.unicode.org/reports/tr11/
// https://unicode.org/reports/tr24/
// https://unicode.org/reports/tr51/
// https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AEast_Asian_Width%3DFullwidth%3A%5D&abb=on&esc=on&g=&i=
// Notes:
// The East_Asian_Width property is not supported (seemingly at all) by JavaScript, so East_Asian_Width=Fullwidth ranges are matched directly:
// https://github.com/tc39/proposal-regexp-unicode-property-escapes/issues/28
// As an alternative to matching by Script names, consider matching East_Asian_Width=Wide (Wide is a superset of Fullwidth) directly as well:
// https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AEast_Asian_Width%3DWide%3A%5D&abb=on&esc=on&g=&i=
const defaultWideCharacterReString = "[\\p{RGI_Emoji}\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}\\u3000\\uFF01-\\uFF60\\uFFE0-\\uFFE6]";
/** /**
* @typedef Column * @typedef Column
@ -21,12 +32,14 @@ const defaultWideCharacterReString = "\\p{RGI_Emoji}";
* *
* @param {string} line Line of text. * @param {string} line Line of text.
* @param {number} column Actual column (1-based). * @param {number} column Actual column (1-based).
* @param {RegExp} wideRe Wide character RegExp. * @param {RegExp} wideCharacterRe Wide character RegExp.
* @returns {number} Effective column (1-based). * @returns {number} Effective column (1-based).
*/ */
function effectiveColumn(line, column, wideRe) { function effectiveColumn(line, column, wideCharacterRe) {
const wideCharacterCount = (line.slice(0, column - 1).match(wideRe) || []).length; const span = line.slice(0, column - 1);
return column + wideCharacterCount; const totalCharacterCount = (span.match(anyCharacterRe) || []).length;
const wideCharacterCount = (span.match(wideCharacterRe) || []).length;
return totalCharacterCount + wideCharacterCount;
} }
/** /**
@ -34,13 +47,13 @@ function effectiveColumn(line, column, wideRe) {
* *
* @param {readonly string[]} lines File/string lines. * @param {readonly string[]} lines File/string lines.
* @param {MicromarkToken} row Micromark row token. * @param {MicromarkToken} row Micromark row token.
* @param {RegExp} wideRe Wide character RegExp. * @param {RegExp} wideCharacterRe Wide character RegExp.
* @returns {Column[]} Divider columns. * @returns {Column[]} Divider columns.
*/ */
function getTableDividerColumns(lines, row, wideRe) { function getTableDividerColumns(lines, row, wideCharacterRe) {
return filterByTypes( return filterByTypes(
row.children, row.children,
[ "tableCellDivider" ]).map((divider) => ({ "actual": divider.startColumn, "effective": effectiveColumn(lines[row.startLine - 1], divider.startColumn, wideRe) }) [ "tableCellDivider" ]).map((divider) => ({ "actual": divider.startColumn, "effective": effectiveColumn(lines[row.startLine - 1], divider.startColumn, wideCharacterRe) })
); );
} }
@ -71,9 +84,7 @@ export default {
const styleAlignedAllowed = (style === "any") || (style === "aligned"); const styleAlignedAllowed = (style === "any") || (style === "aligned");
const styleCompactAllowed = (style === "any") || (style === "compact"); const styleCompactAllowed = (style === "any") || (style === "compact");
const styleTightAllowed = (style === "any") || (style === "tight"); const styleTightAllowed = (style === "any") || (style === "tight");
const wideCharacter = params.config.wide_character; const wideCharacterRe = new RegExp(params.config.wide_character || defaultWideCharacterReString, regExpFlags);
const wideCharacterReString = (wideCharacter === undefined) ? defaultWideCharacterReString : wideCharacter;
const wideCharacterRe = new RegExp(wideCharacterReString, "gv");
// Scan all tables/rows // Scan all tables/rows
const tables = filterByTypesCached([ "table" ]); const tables = filterByTypesCached([ "table" ]);

View file

@ -342,5 +342,6 @@
// Table column style // Table column style
"style": "any", "style": "any",
// RegExp for matching wide character(s) // RegExp for matching wide character(s)
"wide_character": "TBD"
} }
} }

View file

@ -305,3 +305,4 @@ MD060:
# Table column style # Table column style
style: "any" style: "any"
# RegExp for matching wide character(s) # RegExp for matching wide character(s)
wide_character: "TBD"

View file

@ -649,7 +649,7 @@ for (const rule of rules) {
subscheme.properties.wide_character = { subscheme.properties.wide_character = {
"description": "RegExp for matching wide character(s)", "description": "RegExp for matching wide character(s)",
"type": "string", "type": "string",
"default": undefined "default": "TBD"
}; };
break; break;
default: default:

View file

@ -4704,7 +4704,8 @@
}, },
"wide_character": { "wide_character": {
"description": "RegExp for matching wide character(s)", "description": "RegExp for matching wide character(s)",
"type": "string" "type": "string",
"default": "TBD"
} }
} }
} }
@ -4754,7 +4755,8 @@
}, },
"wide_character": { "wide_character": {
"description": "RegExp for matching wide character(s)", "description": "RegExp for matching wide character(s)",
"type": "string" "type": "string",
"default": "TBD"
} }
} }
} }

View file

@ -4704,7 +4704,8 @@
}, },
"wide_character": { "wide_character": {
"description": "RegExp for matching wide character(s)", "description": "RegExp for matching wide character(s)",
"type": "string" "type": "string",
"default": "TBD"
} }
} }
} }
@ -4754,7 +4755,8 @@
}, },
"wide_character": { "wide_character": {
"description": "RegExp for matching wide character(s)", "description": "RegExp for matching wide character(s)",
"type": "string" "type": "string",
"default": "TBD"
} }
} }
} }

View file

@ -1101,7 +1101,7 @@ test("readme", async(t) => {
}); });
test("validateJsonUsingConfigSchemaStrict", async(t) => { test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(221); t.plan(222);
// @ts-ignore // @ts-ignore
const ajv = new Ajv(ajvOptions); const ajv = new Ajv(ajvOptions);
const validateSchemaStrict = ajv.compile(configSchemaStrict); const validateSchemaStrict = ajv.compile(configSchemaStrict);

View file

@ -72651,6 +72651,124 @@ Generated by [AVA](https://avajs.dev).
`, `,
} }
## table-column-style-wide-characters-disable.md
> Snapshot 1
{
errors: [
{
errorContext: null,
errorDetail: 'Table pipe does not align with heading for style "aligned"',
errorRange: [
5,
1,
],
fixInfo: null,
lineNumber: 6,
ruleDescription: 'Table column style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md060.md',
ruleNames: [
'MD060',
'table-column-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: 'Table pipe does not align with heading for style "aligned"',
errorRange: [
10,
1,
],
fixInfo: null,
lineNumber: 6,
ruleDescription: 'Table column style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md060.md',
ruleNames: [
'MD060',
'table-column-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: 'Table pipe does not align with heading for style "aligned"',
errorRange: [
10,
1,
],
fixInfo: null,
lineNumber: 7,
ruleDescription: 'Table column style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md060.md',
ruleNames: [
'MD060',
'table-column-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: 'Table pipe does not align with heading for style "aligned"',
errorRange: [
5,
1,
],
fixInfo: null,
lineNumber: 8,
ruleDescription: 'Table column style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md060.md',
ruleNames: [
'MD060',
'table-column-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: 'Table pipe does not align with heading for style "aligned"',
errorRange: [
9,
1,
],
fixInfo: null,
lineNumber: 8,
ruleDescription: 'Table column style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md060.md',
ruleNames: [
'MD060',
'table-column-style',
],
severity: 'error',
},
],
fixed: `# Table Column Style - Wide Characters (Disable)␊
| NN | WW |␊
| -- | -- |␊
| NN | NN |␊
| W | NN |␊
| NN | W |␊
| W | W |␊
| ✅N | NN |␊
| NN | ✅N |␊
| ✅N | ✅N |␊
| WW | NN |␊
| NN | WW |␊
| WW | WW |␊
{MD060:-10} {MD060:-9} {MD060:-8}␊
<!-- markdownlint-configure-file {␊
"table-column-style": {␊
"style": "aligned",␊
"wide_character": "[]"␊
}␊
} -->␊
`,
}
## table-column-style-wide-characters.md ## table-column-style-wide-characters.md
> Snapshot 1 > Snapshot 1
@ -72689,9 +72807,16 @@ Generated by [AVA](https://avajs.dev).
| MN | ✅ |␊ | MN | ✅ |␊
| ✅ | ✅ |␊ | ✅ | ✅ |␊
## CJK ## Hello world
TODO...␊ | Language | Translation |␊
|---------------------|----------------|␊
| Emoji | 👋🌎 |␊
| Portuguese (Brazil) | Olá mundo |␊
| Turkish | Merhaba dünya |␊
| Chinese (Mandarin) | 你好,世界 |␊
| Japanese | こんにちは世界 |␊
| Korean | 안녕 세상 |␊
<!-- markdownlint-configure-file {␊ <!-- markdownlint-configure-file {␊
"table-column-style": {␊ "table-column-style": {␊

View file

@ -0,0 +1,23 @@
# Table Column Style - Wide Characters (Disable)
| NN | WW |
| -- | -- |
| NN | NN |
| W | NN |
| NN | W |
| W | W |
| ✅N | NN |
| NN | ✅N |
| ✅N | ✅N |
| WW | NN |
| NN | WW |
| WW | WW |
{MD060:-10} {MD060:-9} {MD060:-8}
<!-- markdownlint-configure-file {
"table-column-style": {
"style": "aligned",
"wide_character": "[]"
}
} -->

View file

@ -30,9 +30,16 @@
| MN | ✅ | | MN | ✅ |
| ✅ | ✅ | | ✅ | ✅ |
## CJK ## Hello world
TODO... | Language | Translation |
|---------------------|----------------|
| Emoji | 👋🌎 |
| Portuguese (Brazil) | Olá mundo |
| Turkish | Merhaba dünya |
| Chinese (Mandarin) | 你好,世界 |
| Japanese | こんにちは世界 |
| Korean | 안녕 세상 |
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"table-column-style": { "table-column-style": {