markdownlint 0.38.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +144 -75
  4. package/doc/Rules.md +174 -18
  5. package/doc/md001.md +14 -0
  6. package/doc/md009.md +8 -9
  7. package/doc/md010.md +6 -0
  8. package/doc/md029.md +2 -0
  9. package/doc/md033.md +7 -1
  10. package/doc/md051.md +10 -5
  11. package/doc/md055.md +1 -1
  12. package/doc/md056.md +1 -1
  13. package/doc/md059.md +4 -1
  14. package/doc/md060.md +118 -0
  15. package/helpers/README.md +2 -2
  16. package/helpers/helpers.cjs +171 -15
  17. package/helpers/micromark-helpers.cjs +11 -7
  18. package/helpers/package.json +1 -1
  19. package/lib/cache.mjs +6 -2
  20. package/lib/configuration-strict.d.ts +1351 -205
  21. package/lib/constants.mjs +6 -5
  22. package/lib/defer-require.cjs +2 -0
  23. package/lib/exports.d.mts +1 -2
  24. package/lib/exports.mjs +1 -2
  25. package/lib/markdownit.cjs +2 -1
  26. package/lib/markdownlint.d.mts +101 -51
  27. package/lib/markdownlint.mjs +217 -181
  28. package/lib/md001.mjs +6 -2
  29. package/lib/md004.mjs +7 -18
  30. package/lib/md007.mjs +2 -1
  31. package/lib/md009.mjs +9 -5
  32. package/lib/md010.mjs +1 -1
  33. package/lib/md011.mjs +8 -6
  34. package/lib/md013.mjs +14 -27
  35. package/lib/md022.mjs +5 -2
  36. package/lib/md029.mjs +25 -14
  37. package/lib/md031.mjs +0 -1
  38. package/lib/md032.mjs +3 -1
  39. package/lib/md033.mjs +28 -20
  40. package/lib/md034.mjs +2 -1
  41. package/lib/md036.mjs +5 -3
  42. package/lib/md037.mjs +10 -7
  43. package/lib/md042.mjs +4 -2
  44. package/lib/md043.mjs +2 -2
  45. package/lib/md044.mjs +1 -1
  46. package/lib/md046.mjs +6 -6
  47. package/lib/md053.mjs +1 -1
  48. package/lib/md054.mjs +7 -5
  49. package/lib/md055.mjs +7 -6
  50. package/lib/md056.mjs +1 -1
  51. package/lib/md060.mjs +170 -0
  52. package/lib/micromark-parse.mjs +5 -3
  53. package/lib/node-imports-browser.mjs +2 -0
  54. package/lib/parse-configuration.mjs +5 -4
  55. package/lib/rules.mjs +3 -1
  56. package/package.json +18 -18
  57. package/schema/.markdownlint.jsonc +69 -54
  58. package/schema/.markdownlint.yaml +66 -53
  59. package/schema/markdownlint-config-schema-strict.json +4709 -1537
  60. package/schema/markdownlint-config-schema.json +4709 -1537
package/lib/md055.mjs CHANGED
@@ -3,15 +3,16 @@
3
3
  import { addErrorDetailIf } from "../helpers/helpers.cjs";
4
4
  import { filterByTypesCached } from "./cache.mjs";
5
5
 
6
+ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
7
+ /** @typedef {import("micromark-extension-gfm-table")} */
8
+
6
9
  const whitespaceTypes = new Set([ "linePrefix", "whitespace" ]);
7
- const ignoreWhitespace = (tokens) => tokens.filter(
10
+ const ignoreWhitespace = (/** @type {MicromarkToken[]} */ tokens) => tokens.filter(
8
11
  (token) => !whitespaceTypes.has(token.type)
9
12
  );
10
- const firstOrNothing = (items) => items[0];
11
- const lastOrNothing = (items) => items[items.length - 1];
12
- const makeRange = (start, end) => [ start, end - start + 1 ];
13
-
14
- /** @typedef {import("micromark-extension-gfm-table")} */
13
+ const firstOrNothing = (/** @type {MicromarkToken[]} */ items) => items[0];
14
+ const lastOrNothing = (/** @type {MicromarkToken[]} */ items) => items[items.length - 1];
15
+ const makeRange = (/** @type {number} */ start, /** @type {number} */ end) => [ start, end - start + 1 ];
15
16
 
16
17
  /** @type {import("markdownlint").Rule} */
17
18
  export default {
package/lib/md056.mjs CHANGED
@@ -4,7 +4,7 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs";
4
4
  import { getParentOfType } from "../helpers/micromark-helpers.cjs";
5
5
  import { filterByTypesCached } from "./cache.mjs";
6
6
 
7
- const makeRange = (start, end) => [ start, end - start + 1 ];
7
+ const makeRange = (/** @type {number} */ start, /** @type {number} */ end) => [ start, end - start + 1 ];
8
8
 
9
9
  /** @typedef {import("micromark-extension-gfm-table")} */
10
10
 
package/lib/md060.mjs ADDED
@@ -0,0 +1,170 @@
1
+ // @ts-check
2
+
3
+ import { filterByTypes } from "../helpers/micromark-helpers.cjs";
4
+ import { filterByTypesCached } from "./cache.mjs";
5
+ import stringWidth from "string-width";
6
+
7
+ /** @typedef {import("micromark-extension-gfm-table")} */
8
+ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
9
+ /** @typedef {import("markdownlint").RuleOnErrorInfo} RuleOnErrorInfo */
10
+
11
+ /**
12
+ * Adds a RuleOnErrorInfo object to a list of RuleOnErrorInfo objects.
13
+ *
14
+ * @param {RuleOnErrorInfo[]} errors List of errors.
15
+ * @param {number} lineNumber Line number.
16
+ * @param {number} column Column number.
17
+ * @param {string} detail Detail message.
18
+ */
19
+ function addError(errors, lineNumber, column, detail) {
20
+ errors.push({
21
+ lineNumber,
22
+ detail,
23
+ "range": [ column, 1 ]
24
+ });
25
+ }
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
+
77
+ /** @type {import("markdownlint").Rule} */
78
+ export default {
79
+ "names": [ "MD060", "table-column-style" ],
80
+ "description": "Table column style",
81
+ "tags": [ "table" ],
82
+ "parser": "micromark",
83
+ "function": function MD060(params, onError) {
84
+ const style = String(params.config.style || "any");
85
+ const styleAlignedAllowed = (style === "any") || (style === "aligned");
86
+ const styleCompactAllowed = (style === "any") || (style === "compact");
87
+ const styleTightAllowed = (style === "any") || (style === "tight");
88
+ const alignedDelimiter = !!params.config.aligned_delimiter;
89
+ const lines = params.lines;
90
+
91
+ // Scan all tables/rows
92
+ const tables = filterByTypesCached([ "table" ]);
93
+ for (const table of tables) {
94
+ const rows = filterByTypes(table.children, [ "tableDelimiterRow", "tableRow" ]);
95
+
96
+ // Determine errors for style "aligned"
97
+ /** @type {RuleOnErrorInfo[]} */
98
+ const errorsIfAligned = [];
99
+ if (styleAlignedAllowed) {
100
+ errorsIfAligned.push(...checkStyleAligned(lines, rows, "Table pipe does not align with header for style \"aligned\""));
101
+ }
102
+
103
+ // Determine errors for styles "compact" and "tight"
104
+ /** @type {RuleOnErrorInfo[]} */
105
+ const errorsIfCompact = [];
106
+ /** @type {RuleOnErrorInfo[]} */
107
+ const errorsIfTight = [];
108
+ if (
109
+ (styleCompactAllowed || styleTightAllowed) &&
110
+ !(styleAlignedAllowed && (errorsIfAligned.length === 0))
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
+ }
117
+ for (const row of rows) {
118
+ const tokensOfInterest = filterByTypes(row.children, [ "tableCellDivider", "tableContent", "whitespace" ]);
119
+ for (let i = 0; i < tokensOfInterest.length; i++) {
120
+ const { startColumn, startLine, type } = tokensOfInterest[i];
121
+ if (type === "tableCellDivider") {
122
+ const previous = tokensOfInterest[i - 1];
123
+ if (previous) {
124
+ if (previous.type === "whitespace") {
125
+ if (previous.text.length !== 1) {
126
+ addError(errorsIfCompact, startLine, startColumn, "Table pipe has extra space to the left for style \"compact\"");
127
+ }
128
+ addError(errorsIfTight, startLine, startColumn, "Table pipe has space to the left for style \"tight\"");
129
+ } else {
130
+ addError(errorsIfCompact, startLine, startColumn, "Table pipe is missing space to the left for style \"compact\"");
131
+ }
132
+ }
133
+ const next = tokensOfInterest[i + 1];
134
+ if (next) {
135
+ if (next.type === "whitespace") {
136
+ if (next.endColumn !== row.endColumn) {
137
+ if (next.text.length !== 1) {
138
+ addError(errorsIfCompact, startLine, startColumn, "Table pipe has extra space to the right for style \"compact\"");
139
+ }
140
+ addError(errorsIfTight, startLine, startColumn, "Table pipe has space to the right for style \"tight\"");
141
+ }
142
+ } else {
143
+ addError(errorsIfCompact, startLine, startColumn, "Table pipe is missing space to the right for style \"compact\"");
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Report errors for whatever (allowed) style has the fewest
152
+ let errorInfos = errorsIfAligned;
153
+ if (
154
+ styleCompactAllowed &&
155
+ ((errorsIfCompact.length < errorInfos.length) || !styleAlignedAllowed)
156
+ ) {
157
+ errorInfos = errorsIfCompact;
158
+ }
159
+ if (
160
+ styleTightAllowed &&
161
+ ((errorsIfTight.length < errorInfos.length) || (!styleAlignedAllowed && !styleCompactAllowed))
162
+ ) {
163
+ errorInfos = errorsIfTight;
164
+ }
165
+ for (const errorInfo of errorInfos) {
166
+ onError(errorInfo);
167
+ }
168
+ }
169
+ }
170
+ };
@@ -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
@@ -212,7 +211,9 @@ function parseInternal(
212
211
  const events = getEvents(markdown, micromarkParseOptions);
213
212
 
214
213
  // Create Token objects
214
+ /** @type {MicromarkToken[]} */
215
215
  const document = [];
216
+ /** @type {MicromarkToken[]} */
216
217
  let flatTokens = [];
217
218
  /** @type {MicromarkToken} */
218
219
  const root = {
@@ -282,6 +283,7 @@ function parseInternal(
282
283
  );
283
284
  current.children = tokens;
284
285
  // Avoid stack overflow of Array.push(...spread)
286
+ // @ts-ignore
285
287
  // eslint-disable-next-line unicorn/prefer-spread
286
288
  flatTokens = flatTokens.concat(tokens[flatTokensSymbol]);
287
289
  }
@@ -6,8 +6,10 @@ const throwForSync = () => {
6
6
  };
7
7
 
8
8
  export const fs = {
9
+ // @ts-ignore
9
10
  "access": (path, callback) => callback(getError()),
10
11
  "accessSync": throwForSync,
12
+ // @ts-ignore
11
13
  "readFile": (path, options, callback) => callback(getError()),
12
14
  "readFileSync": throwForSync
13
15
  };
@@ -4,7 +4,7 @@
4
4
  * Result of a call to parseConfiguration.
5
5
  *
6
6
  * @typedef {Object} ParseConfigurationResult
7
- * @property {Object | null} config Configuration object if successful.
7
+ * @property {import("markdownlint").Configuration | null} config Configuration object if successful.
8
8
  * @property {string | null} message Error message if an error occurred.
9
9
  */
10
10
 
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * @param {string} name Name of the configuration file.
15
15
  * @param {string} content Configuration content.
16
- * @param {import("./markdownlint.mjs").ConfigurationParser[]} [parsers] Parsing function(s).
16
+ * @param {import("markdownlint").ConfigurationParser[]} [parsers] Parsing function(s).
17
17
  * @returns {ParseConfigurationResult} Parse configuration result.
18
18
  */
19
19
  export default function parseConfiguration(name, content, parsers) {
@@ -28,8 +28,9 @@ export default function parseConfiguration(name, content, parsers) {
28
28
  config = (result && (typeof result === "object") && !Array.isArray(result)) ? result : {};
29
29
  // Succeeded
30
30
  return false;
31
- } catch (error) {
32
- errors.push(`Parser ${index++}: ${error.message}`);
31
+ // eslint-disable-next-line jsdoc/reject-any-type
32
+ } catch(/** @type {any} */ error) {
33
+ errors.push(`Parser ${index++}: ${error?.message}`);
33
34
  }
34
35
  // Failed, try the next parser
35
36
  return true;
package/lib/rules.mjs CHANGED
@@ -54,6 +54,7 @@ import md055 from "./md055.mjs";
54
54
  import md056 from "./md056.mjs";
55
55
  import md058 from "./md058.mjs";
56
56
  import md059 from "./md059.mjs";
57
+ import md060 from "./md060.mjs";
57
58
 
58
59
  const rules = [
59
60
  md001,
@@ -110,7 +111,8 @@ const rules = [
110
111
  md056,
111
112
  // md057: See https://github.com/markdownlint/markdownlint
112
113
  md058,
113
- md059
114
+ md059,
115
+ md060
114
116
  ];
115
117
  for (const rule of rules) {
116
118
  const name = rule.names[0].toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint",
3
- "version": "0.38.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": {
@@ -39,11 +39,10 @@
39
39
  "build-declaration": "tsc --allowJs --checkJs --declaration --emitDeclarationOnly --module nodenext --outDir dts --rootDir . --target es2015 lib/exports.mjs lib/exports-async.mjs lib/exports-promise.mjs lib/exports-sync.mjs lib/markdownlint.mjs lib/resolve-module.cjs && node scripts/index.mjs copy dts/lib/exports.d.mts lib/exports.d.mts && node scripts/index.mjs copy dts/lib/exports-async.d.mts lib/exports-async.d.mts && node scripts/index.mjs copy dts/lib/exports-promise.d.mts lib/exports-promise.d.mts && node scripts/index.mjs copy dts/lib/exports-sync.d.mts lib/exports-sync.d.mts && node scripts/index.mjs copy dts/lib/markdownlint.d.mts lib/markdownlint.d.mts && node scripts/index.mjs copy dts/lib/resolve-module.d.cts lib/resolve-module.d.cts && node scripts/index.mjs remove dts",
40
40
  "build-demo": "node scripts/index.mjs copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats",
41
41
  "build-docs": "node doc-build/build-rules.mjs",
42
- "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2",
43
42
  "ci": "npm-run-all --continue-on-error --parallel build-demo lint serial-config-docs serial-declaration test-cover && git diff --exit-code",
44
43
  "clone-test-repos-apache-airflow": "cd test-repos && git clone https://github.com/apache/airflow apache-airflow --depth 1 --no-tags --quiet",
45
44
  "clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet",
46
- "clone-test-repos-electron-electron": "cd test-repos && git clone https://github.com/electron/electron electron-electron --depth 1 --no-tags --quiet && cd electron-electron && npm install --ignore-scripts @electron/lint-roller typescript@4",
45
+ "clone-test-repos-electron-electron": "cd test-repos && git clone https://github.com/electron/electron electron-electron --depth 1 --no-tags --quiet && cd electron-electron && node ../../scripts/index.mjs delete package.json && npm install --no-save --ignore-scripts @electron/lint-roller",
47
46
  "clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet",
48
47
  "clone-test-repos-mdn-content": "cd test-repos && git clone https://github.com/mdn/content mdn-content --depth 1 --no-tags --quiet",
49
48
  "clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet",
@@ -54,7 +53,7 @@
54
53
  "clone-test-repos-webpack-webpack-js-org": "cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet",
55
54
  "clone-test-repos": "mkdir test-repos && cd test-repos && npm run clone-test-repos-apache-airflow && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-electron-electron && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mdn-content && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-v8-v8-dev && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org",
56
55
  "declaration": "npm run build-declaration && npm run test-declaration",
57
- "example": "cd example && node standalone.mjs && grunt markdownlint --force && gulp markdownlint",
56
+ "example": "cd example && node standalone.mjs",
58
57
  "lint": "eslint --max-warnings 0",
59
58
  "lint-test-repos": "ava --timeout=10m test/markdownlint-test-repos-*.mjs",
60
59
  "serial-config-docs": "npm run build-config && npm run build-docs",
@@ -81,23 +80,24 @@
81
80
  "micromark-extension-gfm-footnote": "2.1.0",
82
81
  "micromark-extension-gfm-table": "2.1.1",
83
82
  "micromark-extension-math": "3.1.0",
84
- "micromark-util-types": "2.0.2"
83
+ "micromark-util-types": "2.0.2",
84
+ "string-width": "8.1.0"
85
85
  },
86
86
  "devDependencies": {
87
- "@eslint/js": "9.26.0",
88
- "@stylistic/eslint-plugin": "4.2.0",
87
+ "@eslint/js": "9.39.1",
88
+ "@stylistic/eslint-plugin": "5.6.1",
89
89
  "ajv": "8.17.1",
90
- "ava": "6.3.0",
90
+ "ava": "6.4.1",
91
91
  "c8": "10.1.3",
92
92
  "character-entities": "2.0.2",
93
- "eslint": "9.26.0",
94
- "eslint-plugin-jsdoc": "50.6.11",
95
- "eslint-plugin-n": "17.17.0",
96
- "eslint-plugin-regexp": "2.7.0",
97
- "eslint-plugin-unicorn": "59.0.0",
93
+ "eslint": "9.39.1",
94
+ "eslint-plugin-jsdoc": "61.4.1",
95
+ "eslint-plugin-n": "17.23.1",
96
+ "eslint-plugin-regexp": "2.10.0",
97
+ "eslint-plugin-unicorn": "62.0.0",
98
98
  "gemoji": "8.1.0",
99
- "globby": "14.1.0",
100
- "js-yaml": "4.1.0",
99
+ "globby": "16.0.0",
100
+ "js-yaml": "4.1.1",
101
101
  "json-schema-to-typescript": "15.0.4",
102
102
  "jsonc-parser": "3.3.1",
103
103
  "markdown-it": "14.1.0",
@@ -105,12 +105,12 @@
105
105
  "markdown-it-sub": "2.0.0",
106
106
  "markdown-it-sup": "2.0.0",
107
107
  "markdownlint-rule-extended-ascii": "0.2.1",
108
- "nano-spawn": "0.2.0",
108
+ "nano-spawn": "2.0.0",
109
109
  "npm-run-all": "4.1.5",
110
110
  "terser-webpack-plugin": "5.3.14",
111
111
  "toml": "3.0.0",
112
- "typescript": "5.8.3",
113
- "webpack": "5.99.7",
112
+ "typescript": "5.9.3",
113
+ "webpack": "5.103.0",
114
114
  "webpack-cli": "6.0.1"
115
115
  },
116
116
  "keywords": [