markdownlint 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/constants.mjs CHANGED
@@ -11,4 +11,4 @@ export const fixableRuleNames = [
11
11
  "MD054", "MD058"
12
12
  ];
13
13
  export const homepage = "https://github.com/DavidAnson/markdownlint";
14
- export const version = "0.39.0";
14
+ export const version = "0.40.0";
package/lib/md011.mjs CHANGED
@@ -4,6 +4,8 @@ import { addError, hasOverlap } from "../helpers/helpers.cjs";
4
4
  import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
5
5
  import { filterByTypesCached } from "./cache.mjs";
6
6
 
7
+ /** @typedef {import("micromark-extension-math")} */
8
+
7
9
  const reversedLinkRe = /(^|[^\\])\(([^()]+)\)\[([^\]^][^\]]*)\](?!\()/g;
8
10
 
9
11
  /** @type {import("markdownlint").Rule} */
@@ -13,14 +15,14 @@ export default {
13
15
  "tags": [ "links" ],
14
16
  "parser": "micromark",
15
17
  "function": function MD011(params, onError) {
16
- const codeBlockLineNumbers = new Set();
17
- for (const codeBlock of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
18
- addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
18
+ const ignoreBlockLineNumbers = new Set();
19
+ for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "mathFlow" ])) {
20
+ addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
19
21
  }
20
- const codeTexts = filterByTypesCached([ "codeText" ]);
22
+ const ignoreTexts = filterByTypesCached([ "codeText", "mathText" ]);
21
23
  for (const [ lineIndex, line ] of params.lines.entries()) {
22
24
  const lineNumber = lineIndex + 1;
23
- if (!codeBlockLineNumbers.has(lineNumber)) {
25
+ if (!ignoreBlockLineNumbers.has(lineNumber)) {
24
26
  let match = null;
25
27
  while ((match = reversedLinkRe.exec(line)) !== null) {
26
28
  const [ reversedLink, preChar, linkText, linkDestination ] = match;
@@ -32,7 +34,7 @@ export default {
32
34
  const length = match[0].length - preChar.length;
33
35
  /** @type {import("../helpers/helpers.cjs").FileRange} */
34
36
  const range = { "startLine": lineNumber, "startColumn": column, "endLine": lineNumber, "endColumn": column + length - 1 };
35
- if (!codeTexts.some((codeText) => hasOverlap(codeText, range))) {
37
+ if (!ignoreTexts.some((ignoreText) => hasOverlap(ignoreText, range))) {
36
38
  addError(
37
39
  onError,
38
40
  lineNumber,
package/lib/md013.mjs CHANGED
@@ -4,10 +4,8 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs";
4
4
  import { filterByTypesCached, getReferenceLinkImageData } from "./cache.mjs";
5
5
  import { addRangeToSet, getDescendantsByType } from "../helpers/micromark-helpers.cjs";
6
6
 
7
- const longLineRePrefix = "^.{";
8
- const longLineRePostfixRelaxed = "}.*\\s.*$";
9
- const longLineRePostfixStrict = "}.+$";
10
- const sternModeRe = /^(?:[#>\s]*\s)?\S*$/;
7
+ // Regular expression for a line that is not wrappable
8
+ const notWrappableRe = /^(?:[#>\s]*\s)?\S*$/;
11
9
 
12
10
  /** @typedef {import("micromark-extension-gfm-autolink-literal")} */
13
11
  /** @typedef {import("micromark-extension-gfm-table")} */
@@ -20,20 +18,10 @@ export default {
20
18
  "parser": "micromark",
21
19
  "function": function MD013(params, onError) {
22
20
  const lineLength = Number(params.config.line_length || 80);
23
- const headingLineLength =
24
- Number(params.config.heading_line_length || lineLength);
25
- const codeLineLength =
26
- Number(params.config.code_block_line_length || lineLength);
21
+ const headingLineLength = Number(params.config.heading_line_length || lineLength);
22
+ const codeLineLength = Number(params.config.code_block_line_length || lineLength);
27
23
  const strict = !!params.config.strict;
28
24
  const stern = !!params.config.stern;
29
- const longLineRePostfix =
30
- (strict || stern) ? longLineRePostfixStrict : longLineRePostfixRelaxed;
31
- const longLineRe =
32
- new RegExp(longLineRePrefix + lineLength + longLineRePostfix);
33
- const longHeadingLineRe =
34
- new RegExp(longLineRePrefix + headingLineLength + longLineRePostfix);
35
- const longCodeLineRe =
36
- new RegExp(longLineRePrefix + codeLineLength + longLineRePostfix);
37
25
  const codeBlocks = params.config.code_blocks;
38
26
  const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
39
27
  const tables = params.config.tables;
@@ -75,28 +63,27 @@ export default {
75
63
  const isHeading = headingLineNumbers.has(lineNumber);
76
64
  const inCode = codeBlockLineNumbers.has(lineNumber);
77
65
  const inTable = tableLineNumbers.has(lineNumber);
78
- const length = inCode ?
79
- codeLineLength :
80
- (isHeading ? headingLineLength : lineLength);
81
- const lengthRe = inCode ?
82
- longCodeLineRe :
83
- (isHeading ? longHeadingLineRe : longLineRe);
84
- if ((includeCodeBlocks || !inCode) &&
66
+ const maxLength = inCode ? codeLineLength : (isHeading ? headingLineLength : lineLength);
67
+ // If not strict/stern, the last run of non-whitespace is allowed to go
68
+ // beyond the limit as long as it begins within the limit
69
+ const text = (strict || stern) ? line : line.replace(/\S*$/u, "#");
70
+ if ((maxLength > 0) &&
71
+ (includeCodeBlocks || !inCode) &&
85
72
  (includeTables || !inTable) &&
86
73
  (includeHeadings || !isHeading) &&
87
74
  !definitionLineIndices.has(lineIndex) &&
88
75
  (strict ||
89
- (!(stern && sternModeRe.test(line)) &&
76
+ (!(stern && notWrappableRe.test(line)) &&
90
77
  !linkOnlyLineNumbers.has(lineNumber))) &&
91
- lengthRe.test(line)) {
78
+ (text.length > maxLength)) {
92
79
  addErrorDetailIf(
93
80
  onError,
94
81
  lineNumber,
95
- length,
82
+ maxLength,
96
83
  line.length,
97
84
  undefined,
98
85
  undefined,
99
- [ length + 1, line.length - length ]
86
+ [ maxLength + 1, line.length - maxLength ]
100
87
  );
101
88
  }
102
89
  }
package/lib/md060.mjs CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  import { filterByTypes } from "../helpers/micromark-helpers.cjs";
4
4
  import { filterByTypesCached } from "./cache.mjs";
5
+ import stringWidth from "string-width";
5
6
 
6
7
  /** @typedef {import("micromark-extension-gfm-table")} */
8
+ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
7
9
  /** @typedef {import("markdownlint").RuleOnErrorInfo} RuleOnErrorInfo */
8
10
 
9
11
  /**
@@ -22,6 +24,56 @@ function addError(errors, lineNumber, column, detail) {
22
24
  });
23
25
  }
24
26
 
27
+ /**
28
+ * @typedef Column
29
+ * @property {number} actual Actual column (1-based).
30
+ * @property {number} effective Effective column (1-based).
31
+ */
32
+
33
+ /**
34
+ * Gets a list of table cell divider columns.
35
+ *
36
+ * @param {readonly string[]} lines File/string lines.
37
+ * @param {MicromarkToken} row Micromark row token.
38
+ * @returns {Column[]} Divider columns.
39
+ */
40
+ function getTableDividerColumns(lines, row) {
41
+ return filterByTypes(
42
+ row.children,
43
+ [ "tableCellDivider" ]
44
+ ).map(
45
+ (divider) => ({
46
+ "actual": divider.startColumn,
47
+ "effective": stringWidth(lines[row.startLine - 1].slice(0, divider.startColumn - 1))
48
+ })
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Checks the specified table rows for consistency with the "aligned" style.
54
+ *
55
+ * @param {readonly string[]} lines File/string lines.
56
+ * @param {MicromarkToken[]} rows Micromark row tokens.
57
+ * @param {string} detail Detail message.
58
+ * @returns {RuleOnErrorInfo[]} List of errors.
59
+ */
60
+ function checkStyleAligned(lines, rows, detail) {
61
+ /** @type {RuleOnErrorInfo[]} */
62
+ const errorInfos = [];
63
+ const headerRow = rows[0];
64
+ const headerDividerColumns = getTableDividerColumns(lines, headerRow);
65
+ for (const row of rows.slice(1)) {
66
+ const remainingHeaderDividerColumns = new Set(headerDividerColumns.map((column) => column.effective));
67
+ const rowDividerColumns = getTableDividerColumns(lines, row);
68
+ for (const dividerColumn of rowDividerColumns) {
69
+ if ((remainingHeaderDividerColumns.size > 0) && !remainingHeaderDividerColumns.delete(dividerColumn.effective)) {
70
+ addError(errorInfos, row.startLine, dividerColumn.actual, detail);
71
+ }
72
+ }
73
+ }
74
+ return errorInfos;
75
+ }
76
+
25
77
  /** @type {import("markdownlint").Rule} */
26
78
  export default {
27
79
  "names": [ "MD060", "table-column-style" ],
@@ -33,27 +85,19 @@ export default {
33
85
  const styleAlignedAllowed = (style === "any") || (style === "aligned");
34
86
  const styleCompactAllowed = (style === "any") || (style === "compact");
35
87
  const styleTightAllowed = (style === "any") || (style === "tight");
88
+ const alignedDelimiter = !!params.config.aligned_delimiter;
89
+ const lines = params.lines;
36
90
 
37
91
  // Scan all tables/rows
38
92
  const tables = filterByTypesCached([ "table" ]);
39
93
  for (const table of tables) {
40
94
  const rows = filterByTypes(table.children, [ "tableDelimiterRow", "tableRow" ]);
41
- const headingRow = rows[0];
42
95
 
43
96
  // Determine errors for style "aligned"
44
97
  /** @type {RuleOnErrorInfo[]} */
45
98
  const errorsIfAligned = [];
46
99
  if (styleAlignedAllowed) {
47
- const headingDividerColumns = filterByTypes(headingRow.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn);
48
- for (const row of rows.slice(1)) {
49
- const remainingHeadingDividerColumns = new Set(headingDividerColumns);
50
- const rowDividerColumns = filterByTypes(row.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn);
51
- for (const dividerColumn of rowDividerColumns) {
52
- if ((remainingHeadingDividerColumns.size > 0) && !remainingHeadingDividerColumns.delete(dividerColumn)) {
53
- addError(errorsIfAligned, row.startLine, dividerColumn, "Table pipe does not align with heading for style \"aligned\"");
54
- }
55
- }
56
- }
100
+ errorsIfAligned.push(...checkStyleAligned(lines, rows, "Table pipe does not align with header for style \"aligned\""));
57
101
  }
58
102
 
59
103
  // Determine errors for styles "compact" and "tight"
@@ -65,6 +109,11 @@ export default {
65
109
  (styleCompactAllowed || styleTightAllowed) &&
66
110
  !(styleAlignedAllowed && (errorsIfAligned.length === 0))
67
111
  ) {
112
+ if (alignedDelimiter) {
113
+ const errorInfos = checkStyleAligned(lines, rows.slice(0, 2), "Table pipe does not align with header for option \"aligned_delimiter\"");
114
+ errorsIfCompact.push(...errorInfos);
115
+ errorsIfTight.push(...errorInfos);
116
+ }
68
117
  for (const row of rows) {
69
118
  const tokensOfInterest = filterByTypes(row.children, [ "tableCellDivider", "tableContent", "whitespace" ]);
70
119
  for (let i = 0; i < tokensOfInterest.length; i++) {
@@ -142,11 +142,10 @@ export function getEvents(
142
142
  .trim();
143
143
  if ((text.length > 0) && !text.includes("]")) {
144
144
  /** @type {Event[]} */
145
- const artificialEvents = [];
146
- artificialEvents.push(
145
+ const artificialEvents = [
147
146
  [ "enter", undefinedReferenceType, tokenizeContext ],
148
147
  [ "enter", undefinedReference, tokenizeContext ]
149
- );
148
+ ];
150
149
  for (const event of eventsToReplicate) {
151
150
  const [ kind, token ] = event;
152
151
  // Copy token because the current object will get modified by the parser
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "A Node.js style checker and lint tool for Markdown/CommonMark files.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -80,23 +80,24 @@
80
80
  "micromark-extension-gfm-footnote": "2.1.0",
81
81
  "micromark-extension-gfm-table": "2.1.1",
82
82
  "micromark-extension-math": "3.1.0",
83
- "micromark-util-types": "2.0.2"
83
+ "micromark-util-types": "2.0.2",
84
+ "string-width": "8.1.0"
84
85
  },
85
86
  "devDependencies": {
86
- "@eslint/js": "9.37.0",
87
- "@stylistic/eslint-plugin": "5.4.0",
87
+ "@eslint/js": "9.39.1",
88
+ "@stylistic/eslint-plugin": "5.6.1",
88
89
  "ajv": "8.17.1",
89
90
  "ava": "6.4.1",
90
91
  "c8": "10.1.3",
91
92
  "character-entities": "2.0.2",
92
- "eslint": "9.37.0",
93
- "eslint-plugin-jsdoc": "61.1.1",
93
+ "eslint": "9.39.1",
94
+ "eslint-plugin-jsdoc": "61.4.1",
94
95
  "eslint-plugin-n": "17.23.1",
95
96
  "eslint-plugin-regexp": "2.10.0",
96
- "eslint-plugin-unicorn": "61.0.2",
97
+ "eslint-plugin-unicorn": "62.0.0",
97
98
  "gemoji": "8.1.0",
98
- "globby": "15.0.0",
99
- "js-yaml": "4.1.0",
99
+ "globby": "16.0.0",
100
+ "js-yaml": "4.1.1",
100
101
  "json-schema-to-typescript": "15.0.4",
101
102
  "jsonc-parser": "3.3.1",
102
103
  "markdown-it": "14.1.0",
@@ -109,7 +110,7 @@
109
110
  "terser-webpack-plugin": "5.3.14",
110
111
  "toml": "3.0.0",
111
112
  "typescript": "5.9.3",
112
- "webpack": "5.102.1",
113
+ "webpack": "5.103.0",
113
114
  "webpack-cli": "6.0.1"
114
115
  },
115
116
  "keywords": [