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
@@ -24,15 +24,17 @@ function validateRuleList(ruleList, synchronous) {
24
24
  // No need to validate if only using built-in rules
25
25
  return result;
26
26
  }
27
+ /** @type {Object.<string, boolean>} */
27
28
  const allIds = {};
28
29
  for (const [ index, rule ] of ruleList.entries()) {
29
30
  const customIndex = index - rules.length;
30
- // eslint-disable-next-line jsdoc/require-jsdoc
31
- function newError(property, value) {
31
+ // eslint-disable-next-line jsdoc/reject-any-type, jsdoc/require-jsdoc
32
+ function newError(/** @type {string} */ property, /** @type {any} */ value) {
32
33
  return new Error(
33
34
  `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`);
34
35
  }
35
36
  for (const property of [ "names", "tags" ]) {
37
+ // @ts-ignore
36
38
  const value = rule[property];
37
39
  if (!result &&
38
40
  (!value || !Array.isArray(value) || (value.length === 0) ||
@@ -45,6 +47,7 @@ function validateRuleList(ruleList, synchronous) {
45
47
  [ "function", "function" ]
46
48
  ]) {
47
49
  const property = propertyInfo[0];
50
+ // @ts-ignore
48
51
  const value = rule[property];
49
52
  if (!result && (!value || (typeof value !== propertyInfo[1]))) {
50
53
  result = newError(property, value);
@@ -108,9 +111,17 @@ function validateRuleList(ruleList, synchronous) {
108
111
  * @returns {LintResults} New LintResults instance.
109
112
  */
110
113
  function newResults(ruleList) {
111
- const lintResults = {};
112
- // eslint-disable-next-line jsdoc/require-jsdoc
114
+ /**
115
+ * Returns the string representation of a LintResults instance.
116
+ *
117
+ * @param {boolean} useAlias True if rule alias should be used instead of name.
118
+ * @returns {string} String representation of the instance.
119
+ * @this {LintResults}
120
+ */
113
121
  function toString(useAlias) {
122
+ // eslint-disable-next-line consistent-this, unicorn/no-this-assignment
123
+ const lintResults = this;
124
+ /** @type {Object.<string, Rule> | null} */
114
125
  let ruleNameToRule = null;
115
126
  const results = [];
116
127
  const keys = Object.keys(lintResults);
@@ -121,6 +132,7 @@ function newResults(ruleList) {
121
132
  for (const result of fileResults) {
122
133
  const ruleMoniker = result.ruleNames ?
123
134
  result.ruleNames.join("/") :
135
+ // @ts-ignore
124
136
  (result.ruleName + "/" + result.ruleAlias);
125
137
  results.push(
126
138
  file + ": " +
@@ -161,19 +173,29 @@ function newResults(ruleList) {
161
173
  }
162
174
  return results.join("\n");
163
175
  }
176
+ const lintResults = {};
164
177
  Object.defineProperty(lintResults, "toString", { "value": toString });
165
178
  // @ts-ignore
166
179
  return lintResults;
167
180
  }
168
181
 
182
+ /**
183
+ * Result object for removeFrontMatter.
184
+ *
185
+ * @typedef {Object} RemoveFrontMatterResult
186
+ * @property {string} content Markdown content.
187
+ * @property {string[]} frontMatterLines Front matter lines.
188
+ */
189
+
169
190
  /**
170
191
  * Remove front matter (if present at beginning of content).
171
192
  *
172
193
  * @param {string} content Markdown content.
173
194
  * @param {RegExp | null} frontMatter Regular expression to match front matter.
174
- * @returns {Object} Trimmed content and front matter lines.
195
+ * @returns {RemoveFrontMatterResult} Trimmed content and front matter lines.
175
196
  */
176
197
  function removeFrontMatter(content, frontMatter) {
198
+ /** @type {string[]} */
177
199
  let frontMatterLines = [];
178
200
  if (frontMatter) {
179
201
  const frontMatterMatch = content.match(frontMatter);
@@ -200,6 +222,7 @@ function removeFrontMatter(content, frontMatter) {
200
222
  * @returns {Object.<string, string[]>} Map of alias to rule name.
201
223
  */
202
224
  function mapAliasToRuleNames(ruleList) {
225
+ /** @type {Object.<string, string[]>} */
203
226
  const aliasToRuleNames = {};
204
227
  // const tagToRuleNames = {};
205
228
  for (const rule of ruleList) {
@@ -229,44 +252,87 @@ function mapAliasToRuleNames(ruleList) {
229
252
  return aliasToRuleNames;
230
253
  }
231
254
 
255
+ /**
256
+ * Result object for getEffectiveConfig.
257
+ *
258
+ * @typedef {Object} GetEffectiveConfigResult
259
+ * @property {Configuration} effectiveConfig Effective configuration.
260
+ * @property {Map<string, boolean>} rulesEnabled Rules enabled.
261
+ * @property {Map<string, "error" | "warning">} rulesSeverity Rules severity.
262
+ */
263
+
232
264
  /**
233
265
  * Apply (and normalize) configuration object.
234
266
  *
235
267
  * @param {Rule[]} ruleList List of rules.
236
268
  * @param {Configuration} config Configuration object.
237
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
238
- * names.
239
- * @returns {Configuration} Effective configuration.
269
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
270
+ * @returns {GetEffectiveConfigResult} Effective configuration and rule severities.
240
271
  */
241
272
  function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
242
- const defaultKey = Object.keys(config).filter(
243
- (key) => key.toUpperCase() === "DEFAULT"
244
- );
245
- const ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]];
273
+ let ruleDefaultEnable = true;
274
+ /** @type {"error" | "warning"} */
275
+ let ruleDefaultSeverity = "error";
276
+ Object.entries(config).every(([ key, value ]) => {
277
+ if (key.toUpperCase() === "DEFAULT") {
278
+ ruleDefaultEnable = !!value;
279
+ if (value === "warning") {
280
+ ruleDefaultSeverity = "warning";
281
+ }
282
+ return false;
283
+ }
284
+ return true;
285
+ });
246
286
  /** @type {Configuration} */
247
287
  const effectiveConfig = {};
248
- for (const rule of ruleList) {
249
- const ruleName = rule.names[0].toUpperCase();
250
- effectiveConfig[ruleName] = ruleDefault;
288
+ /** @type {Map<string, boolean>} */
289
+ const rulesEnabled = new Map();
290
+ /** @type {Map<string, "error" | "warning">} */
291
+ const rulesSeverity = new Map();
292
+ const emptyObject = Object.freeze({});
293
+ for (const ruleName of ruleList.map((rule) => rule.names[0].toUpperCase())) {
294
+ effectiveConfig[ruleName] = emptyObject;
295
+ rulesEnabled.set(ruleName, ruleDefaultEnable);
296
+ rulesSeverity.set(ruleName, ruleDefaultSeverity);
251
297
  }
252
298
  // for (const ruleName of deprecatedRuleNames) {
253
299
  // effectiveConfig[ruleName] = false;
254
300
  // }
255
- for (const key of Object.keys(config)) {
256
- let value = config[key];
301
+ for (const [ key, value ] of Object.entries(config)) {
302
+ const keyUpper = key.toUpperCase();
303
+ /** @type {boolean} */
304
+ let enabled = false;
305
+ /** @type {"error" | "warning"} */
306
+ let severity = "error";
307
+ let effectiveValue = {};
257
308
  if (value) {
258
- if (!(value instanceof Object)) {
259
- value = {};
309
+ if (value instanceof Object) {
310
+ /** @type {{ enabled?: boolean, severity?: "error" | "warning" }} */
311
+ const valueObject = value;
312
+ enabled = (valueObject.enabled === undefined) ? true : !!valueObject.enabled;
313
+ severity = (valueObject.severity === "warning") ? "warning" : "error";
314
+ effectiveValue = Object.fromEntries(
315
+ Object.entries(value).filter(
316
+ ([ k ]) => (k !== "enabled") && (k !== "severity")
317
+ )
318
+ );
319
+ } else {
320
+ enabled = true;
321
+ severity = (value === "warning") ? "warning" : "error";
260
322
  }
261
- } else {
262
- value = false;
263
323
  }
264
- const keyUpper = key.toUpperCase();
265
324
  for (const ruleName of (aliasToRuleNames[keyUpper] || [])) {
266
- effectiveConfig[ruleName] = value;
325
+ Object.freeze(effectiveValue);
326
+ effectiveConfig[ruleName] = effectiveValue;
327
+ rulesEnabled.set(ruleName, enabled);
328
+ rulesSeverity.set(ruleName, severity);
267
329
  }
268
330
  }
269
- return effectiveConfig;
331
+ return {
332
+ effectiveConfig,
333
+ rulesEnabled,
334
+ rulesSeverity
335
+ };
270
336
  }
271
337
 
272
338
  /**
@@ -274,8 +340,9 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
274
340
  *
275
341
  * @typedef {Object} EnabledRulesPerLineNumberResult
276
342
  * @property {Configuration} effectiveConfig Effective configuration.
277
- * @property {any[]} enabledRulesPerLineNumber Enabled rules per line number.
343
+ * @property {Map<string, boolean>[]} enabledRulesPerLineNumber Enabled rules per line number.
278
344
  * @property {Rule[]} enabledRuleList Enabled rule list.
345
+ * @property {Map<string, "error" | "warning">} rulesSeverity Rules severity.
279
346
  */
280
347
 
281
348
  /**
@@ -287,8 +354,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
287
354
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
288
355
  * @param {Configuration} config Configuration object.
289
356
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
290
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
291
- * names.
357
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
292
358
  * @returns {EnabledRulesPerLineNumberResult} Effective configuration and enabled rules per line number.
293
359
  */
294
360
  function getEnabledRulesPerLineNumber(
@@ -300,13 +366,14 @@ function getEnabledRulesPerLineNumber(
300
366
  configParsers,
301
367
  aliasToRuleNames) {
302
368
  // Shared variables
303
- let enabledRules = {};
304
- let capturedRules = {};
305
- const allRuleNames = [];
369
+ /** @type {Map<string, boolean>} */
370
+ let enabledRules = new Map();
371
+ /** @type {Map<string, boolean>} */
372
+ let capturedRules = enabledRules;
306
373
  const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
307
374
  // Helper functions
308
375
  // eslint-disable-next-line jsdoc/require-jsdoc
309
- function handleInlineConfig(input, forEachMatch, forEachLine) {
376
+ function handleInlineConfig(/** @type {string[]} */ input, /** @type {(act: string, par: string, ind: number) => void} */ forEachMatch, /** @type {(() => void)|undefined} */ forEachLine = undefined) {
310
377
  for (const [ lineIndex, line ] of input.entries()) {
311
378
  if (!noInlineConfig) {
312
379
  let match = null;
@@ -327,7 +394,7 @@ function getEnabledRulesPerLineNumber(
327
394
  }
328
395
  }
329
396
  // eslint-disable-next-line jsdoc/require-jsdoc
330
- function configureFile(action, parameter) {
397
+ function configureFile(/** @type {string} */ action, /** @type {string} */ parameter) {
331
398
  if (action === "CONFIGURE-FILE") {
332
399
  const { "config": parsed } = parseConfiguration(
333
400
  "CONFIGURE-FILE", parameter, configParsers
@@ -341,26 +408,27 @@ function getEnabledRulesPerLineNumber(
341
408
  }
342
409
  }
343
410
  // eslint-disable-next-line jsdoc/require-jsdoc
344
- function applyEnableDisable(action, parameter, state) {
345
- state = { ...state };
411
+ function applyEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {Map<string, boolean>} */ state) {
412
+ state = new Map(state);
346
413
  const enabled = (action.startsWith("ENABLE"));
347
414
  const trimmed = parameter && parameter.trim();
415
+ // eslint-disable-next-line no-use-before-define
348
416
  const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames;
349
417
  for (const nameUpper of items) {
350
418
  for (const ruleName of (aliasToRuleNames[nameUpper] || [])) {
351
- state[ruleName] = enabled;
419
+ state.set(ruleName, enabled);
352
420
  }
353
421
  }
354
422
  return state;
355
423
  }
356
424
  // eslint-disable-next-line jsdoc/require-jsdoc
357
- function enableDisableFile(action, parameter) {
425
+ function enableDisableFile(/** @type {string} */ action, /** @type {string} */ parameter) {
358
426
  if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
359
427
  enabledRules = applyEnableDisable(action, parameter, enabledRules);
360
428
  }
361
429
  }
362
430
  // eslint-disable-next-line jsdoc/require-jsdoc
363
- function captureRestoreEnableDisable(action, parameter) {
431
+ function captureRestoreEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter) {
364
432
  if (action === "CAPTURE") {
365
433
  capturedRules = enabledRules;
366
434
  } else if (action === "RESTORE") {
@@ -374,7 +442,7 @@ function getEnabledRulesPerLineNumber(
374
442
  enabledRulesPerLineNumber.push(enabledRules);
375
443
  }
376
444
  // eslint-disable-next-line jsdoc/require-jsdoc
377
- function disableLineNextLine(action, parameter, lineNumber) {
445
+ function disableLineNextLine(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {number} */ lineNumber) {
378
446
  const disableLine = (action === "DISABLE-LINE");
379
447
  const disableNextLine = (action === "DISABLE-NEXT-LINE");
380
448
  if (disableLine || disableNextLine) {
@@ -390,29 +458,24 @@ function getEnabledRulesPerLineNumber(
390
458
  }
391
459
  // Handle inline comments
392
460
  handleInlineConfig([ lines.join("\n") ], configureFile);
393
- const effectiveConfig = getEffectiveConfig(
394
- ruleList, config, aliasToRuleNames);
395
- for (const rule of ruleList) {
396
- const ruleName = rule.names[0].toUpperCase();
397
- allRuleNames.push(ruleName);
398
- enabledRules[ruleName] = !!effectiveConfig[ruleName];
399
- }
461
+ const { effectiveConfig, rulesEnabled, rulesSeverity } = getEffectiveConfig(ruleList, config, aliasToRuleNames);
462
+ const allRuleNames = [ ...rulesEnabled.keys() ];
463
+ enabledRules = new Map(rulesEnabled);
400
464
  capturedRules = enabledRules;
401
465
  handleInlineConfig(lines, enableDisableFile);
402
466
  handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
403
467
  handleInlineConfig(lines, disableLineNextLine);
404
468
  // Create the list of rules that are used at least once
405
- const enabledRuleList = [];
406
- for (const [ index, ruleName ] of allRuleNames.entries()) {
407
- if (enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine[ruleName])) {
408
- enabledRuleList.push(ruleList[index]);
409
- }
410
- }
469
+ const enabledRuleList = ruleList.filter((rule) => {
470
+ const ruleName = rule.names[0].toUpperCase();
471
+ return enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine.get(ruleName));
472
+ });
411
473
  // Return results
412
474
  return {
413
475
  effectiveConfig,
414
476
  enabledRulesPerLineNumber,
415
- enabledRuleList
477
+ enabledRuleList,
478
+ rulesSeverity
416
479
  };
417
480
  }
418
481
 
@@ -429,7 +492,6 @@ function getEnabledRulesPerLineNumber(
429
492
  * @param {RegExp | null} frontMatter Regular expression for front matter.
430
493
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
431
494
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
432
- * @param {number} resultVersion Version of the LintResults object to return.
433
495
  * @param {boolean} synchronous Whether to execute synchronously.
434
496
  * @param {LintContentCallback} callback Callback (err, result) function.
435
497
  * @returns {void}
@@ -445,11 +507,11 @@ function lintContent(
445
507
  frontMatter,
446
508
  handleRuleFailures,
447
509
  noInlineConfig,
448
- resultVersion,
449
510
  synchronous,
450
511
  callback) {
451
512
  // Provide a consistent error-reporting callback
452
- const callbackError = (error) => callback(error instanceof Error ? error : new Error(error));
513
+ // eslint-disable-next-line jsdoc/reject-any-type
514
+ const callbackError = (/** @type {any} */ error) => callback(error instanceof Error ? error : new Error(error));
453
515
  // Remove UTF-8 byte order marker (if present)
454
516
  content = content.replace(/^\uFEFF/, "");
455
517
  // Remove front matter
@@ -457,7 +519,7 @@ function lintContent(
457
519
  const { frontMatterLines } = removeFrontMatterResult;
458
520
  content = removeFrontMatterResult.content;
459
521
  // Get enabled rules per line (with HTML comments present)
460
- const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList } =
522
+ const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList, rulesSeverity } =
461
523
  getEnabledRulesPerLineNumber(
462
524
  ruleList,
463
525
  content.split(helpers.newLineRe),
@@ -484,7 +546,7 @@ function lintContent(
484
546
  // Parse content into lines and get markdown-it tokens
485
547
  const lines = content.split(helpers.newLineRe);
486
548
  // Function to run after fetching markdown-it tokens (when needed)
487
- const lintContentInternal = (markdownitTokens) => {
549
+ const lintContentInternal = (/** @type {MarkdownItToken[]} */ markdownitTokens) => {
488
550
  // Create (frozen) parameters for rules
489
551
  /** @type {MarkdownParsers} */
490
552
  // @ts-ignore
@@ -515,7 +577,8 @@ function lintContent(
515
577
  "config": null
516
578
  });
517
579
  // Function to run for each rule
518
- let results = [];
580
+ /** @type {LintError[]} */
581
+ const results = [];
519
582
  /**
520
583
  * @param {Rule} rule Rule.
521
584
  * @returns {Promise<void> | null} Promise.
@@ -533,19 +596,21 @@ function lintContent(
533
596
  } else if (rule.parser === "micromark") {
534
597
  parsers = parsersMicromark;
535
598
  }
536
- const params = {
599
+ const params = Object.freeze({
537
600
  ...paramsBase,
538
601
  ...tokens,
539
602
  parsers,
603
+ /** @type {RuleConfiguration} */
604
+ // @ts-ignore
540
605
  "config": effectiveConfig[ruleName]
541
- };
606
+ });
542
607
  // eslint-disable-next-line jsdoc/require-jsdoc
543
- function throwError(property) {
608
+ function throwError(/** @type {string} */ property) {
544
609
  throw new Error(
545
610
  `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
546
611
  }
547
612
  // eslint-disable-next-line jsdoc/require-jsdoc
548
- function onError(errorInfo) {
613
+ function onError(/** @type {RuleOnErrorInfo} */ errorInfo) {
549
614
  if (!errorInfo ||
550
615
  !helpers.isNumber(errorInfo.lineNumber) ||
551
616
  (errorInfo.lineNumber < 1) ||
@@ -553,7 +618,7 @@ function lintContent(
553
618
  throwError("lineNumber");
554
619
  }
555
620
  const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
556
- if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
621
+ if (!enabledRulesPerLineNumber[lineNumber].get(ruleName)) {
557
622
  return;
558
623
  }
559
624
  if (errorInfo.detail &&
@@ -623,17 +688,19 @@ function lintContent(
623
688
  const information = errorInfo.information || rule.information;
624
689
  results.push({
625
690
  lineNumber,
626
- "ruleName": rule.names[0],
627
691
  "ruleNames": rule.names,
628
692
  "ruleDescription": rule.description,
629
693
  "ruleInformation": information ? information.href : null,
630
- "errorDetail": errorInfo.detail || null,
631
- "errorContext": errorInfo.context || null,
694
+ "errorDetail": errorInfo.detail?.replace(helpers.newLineRe, " ") || null,
695
+ "errorContext": errorInfo.context?.replace(helpers.newLineRe, " ") || null,
632
696
  "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
633
- "fixInfo": fixInfo ? cleanFixInfo : null
697
+ "fixInfo": fixInfo ? cleanFixInfo : null,
698
+ // @ts-ignore
699
+ "severity": rulesSeverity.get(ruleName)
634
700
  });
635
701
  }
636
702
  // Call (possibly external) rule function to report errors
703
+ // @ts-ignore
637
704
  const catchCallsOnError = (error) => onError({
638
705
  "lineNumber": 1,
639
706
  "detail": `This rule threw an exception: ${error.message || error}`
@@ -650,7 +717,7 @@ function lintContent(
650
717
  // Synchronous rule
651
718
  try {
652
719
  invokeRuleFunction();
653
- } catch (error) {
720
+ } catch(error) {
654
721
  if (handleRuleFailures) {
655
722
  catchCallsOnError(error);
656
723
  } else {
@@ -662,46 +729,9 @@ function lintContent(
662
729
  const formatResults = () => {
663
730
  // Sort results by rule name by line number
664
731
  results.sort((a, b) => (
665
- a.ruleName.localeCompare(b.ruleName) ||
732
+ a.ruleNames[0].localeCompare(b.ruleNames[0]) ||
666
733
  a.lineNumber - b.lineNumber
667
734
  ));
668
- if (resultVersion < 3) {
669
- // Remove fixInfo and multiple errors for the same rule and line number
670
- const noPrevious = {
671
- "ruleName": null,
672
- "lineNumber": -1
673
- };
674
- results = results.filter((error, index, array) => {
675
- delete error.fixInfo;
676
- const previous = array[index - 1] || noPrevious;
677
- return (
678
- (error.ruleName !== previous.ruleName) ||
679
- (error.lineNumber !== previous.lineNumber)
680
- );
681
- });
682
- }
683
- if (resultVersion === 0) {
684
- // Return a dictionary of rule->[line numbers]
685
- const dictionary = {};
686
- for (const error of results) {
687
- const ruleLines = dictionary[error.ruleName] || [];
688
- ruleLines.push(error.lineNumber);
689
- dictionary[error.ruleName] = ruleLines;
690
- }
691
- // @ts-ignore
692
- results = dictionary;
693
- } else if (resultVersion === 1) {
694
- // Use ruleAlias instead of ruleNames
695
- for (const error of results) {
696
- error.ruleAlias = error.ruleNames[1] || error.ruleName;
697
- delete error.ruleNames;
698
- }
699
- } else {
700
- // resultVersion 2 or 3: Remove unwanted ruleName
701
- for (const error of results) {
702
- delete error.ruleName;
703
- }
704
- }
705
735
  return results;
706
736
  };
707
737
  // Run all rules
@@ -721,7 +751,7 @@ function lintContent(
721
751
  } else {
722
752
  callbackSuccess();
723
753
  }
724
- } catch (error) {
754
+ } catch(error) {
725
755
  callbackError(error);
726
756
  } finally {
727
757
  cacheInitialize();
@@ -759,8 +789,7 @@ function lintContent(
759
789
  * @param {RegExp | null} frontMatter Regular expression for front matter.
760
790
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
761
791
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
762
- * @param {number} resultVersion Version of the LintResults object to return.
763
- * @param {Object} fs File system implementation.
792
+ * @param {FsLike} fs File system implementation.
764
793
  * @param {boolean} synchronous Whether to execute synchronously.
765
794
  * @param {LintContentCallback} callback Callback (err, result) function.
766
795
  * @returns {void}
@@ -775,12 +804,11 @@ function lintFile(
775
804
  frontMatter,
776
805
  handleRuleFailures,
777
806
  noInlineConfig,
778
- resultVersion,
779
807
  fs,
780
808
  synchronous,
781
809
  callback) {
782
810
  // eslint-disable-next-line jsdoc/require-jsdoc
783
- function lintContentWrapper(err, content) {
811
+ function lintContentWrapper(/** @type {Error | null} */ err, /** @type {string} */ content) {
784
812
  if (err) {
785
813
  return callback(err);
786
814
  }
@@ -795,7 +823,6 @@ function lintFile(
795
823
  frontMatter,
796
824
  handleRuleFailures,
797
825
  noInlineConfig,
798
- resultVersion,
799
826
  synchronous,
800
827
  callback
801
828
  );
@@ -820,6 +847,8 @@ function lintInput(options, synchronous, callback) {
820
847
  // Normalize inputs
821
848
  options = options || {};
822
849
  callback = callback || function noop() {};
850
+ /** @type {Rule[]} */
851
+ // @ts-ignore
823
852
  const customRuleList =
824
853
  [ options.customRules || [] ]
825
854
  .flat()
@@ -839,6 +868,7 @@ function lintInput(options, synchronous, callback) {
839
868
  callback(ruleErr);
840
869
  return;
841
870
  }
871
+ /** @type {string[]} */
842
872
  let files = [];
843
873
  if (Array.isArray(options.files)) {
844
874
  files = [ ...options.files ];
@@ -854,12 +884,14 @@ function lintInput(options, synchronous, callback) {
854
884
  options.frontMatter;
855
885
  const handleRuleFailures = !!options.handleRuleFailures;
856
886
  const noInlineConfig = !!options.noInlineConfig;
857
- const resultVersion = (options.resultVersion === undefined) ?
858
- 3 :
859
- options.resultVersion;
887
+ // @ts-ignore
888
+ // eslint-disable-next-line dot-notation
889
+ const resultVersion = (options["resultVersion"] === undefined) ? 3 : options["resultVersion"];
860
890
  const markdownItFactory =
861
891
  options.markdownItFactory ||
862
892
  (() => { throw new Error("The option 'markdownItFactory' was required (due to the option 'customRules' including a rule requiring the 'markdown-it' parser), but 'markdownItFactory' was not set."); });
893
+ /** @type {FsLike} */
894
+ // @ts-ignore
863
895
  const fs = options.fs || nodeFs;
864
896
  const aliasToRuleNames = mapAliasToRuleNames(ruleList);
865
897
  const results = newResults(ruleList);
@@ -867,14 +899,16 @@ function lintInput(options, synchronous, callback) {
867
899
  let concurrency = 0;
868
900
  // eslint-disable-next-line jsdoc/require-jsdoc
869
901
  function lintWorker() {
870
- let currentItem = null;
902
+ /** @type {string | undefined} */
903
+ let currentItem = undefined;
871
904
  // eslint-disable-next-line jsdoc/require-jsdoc
872
- function lintWorkerCallback(err, result) {
905
+ function lintWorkerCallback(/** @type {Error | null} */ err, /** @type {LintError[] | undefined} */ result) {
873
906
  concurrency--;
874
907
  if (err) {
875
908
  done = true;
876
909
  return callback(err);
877
910
  }
911
+ // @ts-ignore
878
912
  results[currentItem] = result;
879
913
  if (!synchronous) {
880
914
  lintWorker();
@@ -883,10 +917,9 @@ function lintInput(options, synchronous, callback) {
883
917
  }
884
918
  if (done) {
885
919
  // Abort for error or nothing left to do
886
- } else if (files.length > 0) {
920
+ } else if ((currentItem = files.shift())) {
887
921
  // Lint next file
888
922
  concurrency++;
889
- currentItem = files.shift();
890
923
  lintFile(
891
924
  ruleList,
892
925
  aliasToRuleNames,
@@ -897,7 +930,6 @@ function lintInput(options, synchronous, callback) {
897
930
  frontMatter,
898
931
  handleRuleFailures,
899
932
  noInlineConfig,
900
- resultVersion,
901
933
  fs,
902
934
  synchronous,
903
935
  lintWorkerCallback
@@ -916,14 +948,22 @@ function lintInput(options, synchronous, callback) {
916
948
  frontMatter,
917
949
  handleRuleFailures,
918
950
  noInlineConfig,
919
- resultVersion,
920
951
  synchronous,
921
952
  lintWorkerCallback
922
953
  );
923
954
  } else if (concurrency === 0) {
924
955
  // Finish
925
956
  done = true;
926
- return callback(null, results);
957
+ // Deprecated: Convert results to specified resultVersion
958
+ let convertedResults = results;
959
+ if (resultVersion === 0) {
960
+ convertedResults = helpers.convertToResultVersion0(results);
961
+ } else if (resultVersion === 1) {
962
+ convertedResults = helpers.convertToResultVersion1(results);
963
+ } else if (resultVersion === 2) {
964
+ convertedResults = helpers.convertToResultVersion2(results);
965
+ }
966
+ return callback(null, convertedResults);
927
967
  }
928
968
  return null;
929
969
  }
@@ -993,15 +1033,24 @@ export function lintSync(options) {
993
1033
  return results;
994
1034
  }
995
1035
 
1036
+ /**
1037
+ * Node fs instance (or compatible object).
1038
+ *
1039
+ * @typedef FsLike
1040
+ * @property {(path: string, callback: (err: Error) => void) => void} access access method.
1041
+ * @property {(path: string) => void} accessSync accessSync method.
1042
+ * @property {(path: string, encoding: string, callback: (err: Error, data: string) => void) => void} readFile readFile method.
1043
+ * @property {(path: string, encoding: string) => string} readFileSync readFileSync method.
1044
+ */
1045
+
996
1046
  /**
997
1047
  * Resolve referenced "extends" path in a configuration file
998
1048
  * using path.resolve() with require.resolve() as a fallback.
999
1049
  *
1000
1050
  * @param {string} configFile Configuration file name.
1001
1051
  * @param {string} referenceId Referenced identifier to resolve.
1002
- * @param {Object} fs File system implementation.
1003
- * @param {ResolveConfigExtendsCallback} callback Callback (err, result)
1004
- * function.
1052
+ * @param {FsLike} fs File system implementation.
1053
+ * @param {ResolveConfigExtendsCallback} callback Callback (err, result) function.
1005
1054
  * @returns {void}
1006
1055
  */
1007
1056
  function resolveConfigExtends(configFile, referenceId, fs, callback) {
@@ -1029,7 +1078,7 @@ function resolveConfigExtends(configFile, referenceId, fs, callback) {
1029
1078
  *
1030
1079
  * @param {string} configFile Configuration file name.
1031
1080
  * @param {string} referenceId Referenced identifier to resolve.
1032
- * @param {Object} fs File system implementation.
1081
+ * @param {FsLike} fs File system implementation.
1033
1082
  * @returns {string} Resolved path to file.
1034
1083
  */
1035
1084
  function resolveConfigExtendsSync(configFile, referenceId, fs) {
@@ -1054,9 +1103,8 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) {
1054
1103
  *
1055
1104
  * @param {Configuration} config Configuration object.
1056
1105
  * @param {string} file Configuration file name.
1057
- * @param {ConfigurationParser[] | undefined} parsers Parsing
1058
- * function(s).
1059
- * @param {Object} fs File system implementation.
1106
+ * @param {ConfigurationParser[] | undefined} parsers Parsing function(s).
1107
+ * @param {FsLike} fs File system implementation.
1060
1108
  * @param {ReadConfigCallback} callback Callback (err, result) function.
1061
1109
  * @returns {void}
1062
1110
  */
@@ -1096,7 +1144,7 @@ function extendConfig(config, file, parsers, fs, callback) {
1096
1144
  * @param {Configuration} config Configuration object.
1097
1145
  * @param {string} file Configuration file name.
1098
1146
  * @param {ConfigurationParser[] | undefined} parsers Parsing function(s).
1099
- * @param {Object} fs File system implementation.
1147
+ * @param {FsLike} fs File system implementation.
1100
1148
  * @returns {Promise<Configuration>} Configuration object.
1101
1149
  */
1102
1150
  export function extendConfigPromise(config, file, parsers, fs) {
@@ -1115,16 +1163,17 @@ export function extendConfigPromise(config, file, parsers, fs) {
1115
1163
  * Read specified configuration file.
1116
1164
  *
1117
1165
  * @param {string} file Configuration file name.
1118
- * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing
1119
- * function(s).
1120
- * @param {Object} [fs] File system implementation.
1166
+ * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing function(s).
1167
+ * @param {FsLike | ReadConfigCallback} [fs] File system implementation.
1121
1168
  * @param {ReadConfigCallback} [callback] Callback (err, result) function.
1122
1169
  * @returns {void}
1123
1170
  */
1124
1171
  export function readConfigAsync(file, parsers, fs, callback) {
1125
1172
  if (!callback) {
1126
1173
  if (fs) {
1174
+ // @ts-ignore
1127
1175
  callback = fs;
1176
+ // @ts-ignore
1128
1177
  fs = null;
1129
1178
  } else {
1130
1179
  // @ts-ignore
@@ -1133,12 +1182,12 @@ export function readConfigAsync(file, parsers, fs, callback) {
1133
1182
  parsers = null;
1134
1183
  }
1135
1184
  }
1136
- if (!fs) {
1137
- fs = nodeFs;
1138
- }
1185
+ /** @type {FsLike} */
1186
+ // @ts-ignore
1187
+ const fsLike = fs || nodeFs;
1139
1188
  // Read file
1140
1189
  file = helpers.expandTildePath(file, os);
1141
- fs.readFile(file, "utf8", (err, content) => {
1190
+ fsLike.readFile(file, "utf8", (err, content) => {
1142
1191
  if (err) {
1143
1192
  // @ts-ignore
1144
1193
  return callback(err);
@@ -1152,7 +1201,7 @@ export function readConfigAsync(file, parsers, fs, callback) {
1152
1201
  }
1153
1202
  // Extend configuration
1154
1203
  // @ts-ignore
1155
- return extendConfig(config, file, parsers, fs, callback);
1204
+ return extendConfig(config, file, parsers, fsLike, callback);
1156
1205
  });
1157
1206
  }
1158
1207
 
@@ -1161,7 +1210,7 @@ export function readConfigAsync(file, parsers, fs, callback) {
1161
1210
  *
1162
1211
  * @param {string} file Configuration file name.
1163
1212
  * @param {ConfigurationParser[]} [parsers] Parsing function(s).
1164
- * @param {Object} [fs] File system implementation.
1213
+ * @param {FsLike} [fs] File system implementation.
1165
1214
  * @returns {Promise<Configuration>} Configuration object.
1166
1215
  */
1167
1216
  export function readConfigPromise(file, parsers, fs) {
@@ -1181,16 +1230,16 @@ export function readConfigPromise(file, parsers, fs) {
1181
1230
  *
1182
1231
  * @param {string} file Configuration file name.
1183
1232
  * @param {ConfigurationParser[]} [parsers] Parsing function(s).
1184
- * @param {Object} [fs] File system implementation.
1233
+ * @param {FsLike} [fs] File system implementation.
1185
1234
  * @returns {Configuration} Configuration object.
1186
1235
  */
1187
1236
  export function readConfigSync(file, parsers, fs) {
1188
- if (!fs) {
1189
- fs = nodeFs;
1190
- }
1237
+ /** @type {FsLike} */
1238
+ // @ts-ignore
1239
+ const fsLike = fs || nodeFs;
1191
1240
  // Read file
1192
1241
  file = helpers.expandTildePath(file, os);
1193
- const content = fs.readFileSync(file, "utf8");
1242
+ const content = fsLike.readFileSync(file, "utf8");
1194
1243
  // Try to parse file
1195
1244
  const { config, message } = parseConfiguration(file, content, parsers);
1196
1245
  if (!config) {
@@ -1204,7 +1253,7 @@ export function readConfigSync(file, parsers, fs) {
1204
1253
  const resolvedExtends = resolveConfigExtendsSync(
1205
1254
  file,
1206
1255
  helpers.expandTildePath(configExtends, os),
1207
- fs
1256
+ fsLike
1208
1257
  );
1209
1258
  return {
1210
1259
  ...readConfigSync(resolvedExtends, parsers, fs),
@@ -1217,9 +1266,9 @@ export function readConfigSync(file, parsers, fs) {
1217
1266
  /**
1218
1267
  * Normalizes the fields of a RuleOnErrorFixInfo instance.
1219
1268
  *
1220
- * @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
1269
+ * @param {FixInfo} fixInfo RuleOnErrorFixInfo instance.
1221
1270
  * @param {number} [lineNumber] Line number.
1222
- * @returns {RuleOnErrorFixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
1271
+ * @returns {FixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
1223
1272
  */
1224
1273
  function normalizeFixInfo(fixInfo, lineNumber = 0) {
1225
1274
  return {
@@ -1234,7 +1283,7 @@ function normalizeFixInfo(fixInfo, lineNumber = 0) {
1234
1283
  * Applies the specified fix to a Markdown content line.
1235
1284
  *
1236
1285
  * @param {string} line Line of Markdown content.
1237
- * @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
1286
+ * @param {FixInfo} fixInfo FixInfo instance.
1238
1287
  * @param {string} [lineEnding] Line ending to use.
1239
1288
  * @returns {string | null} Fixed content or null if deleted.
1240
1289
  */
@@ -1250,7 +1299,7 @@ export function applyFix(line, fixInfo, lineEnding = "\n") {
1250
1299
  * Applies as many of the specified fixes as possible to Markdown content.
1251
1300
  *
1252
1301
  * @param {string} input Lines of Markdown content.
1253
- * @param {RuleOnErrorInfo[]} errors RuleOnErrorInfo instances.
1302
+ * @param {LintError[]} errors LintError instances.
1254
1303
  * @returns {string} Fixed content.
1255
1304
  */
1256
1305
  export function applyFixes(input, errors) {
@@ -1346,8 +1395,6 @@ export function getVersion() {
1346
1395
  * @returns {void}
1347
1396
  */
1348
1397
 
1349
- /* eslint-disable jsdoc/valid-types */
1350
-
1351
1398
  /**
1352
1399
  * Rule parameters.
1353
1400
  *
@@ -1360,8 +1407,6 @@ export function getVersion() {
1360
1407
  * @property {string} version Version of the markdownlint library.
1361
1408
  */
1362
1409
 
1363
- /* eslint-enable jsdoc/valid-types */
1364
-
1365
1410
  /**
1366
1411
  * Markdown parser data.
1367
1412
  *
@@ -1451,16 +1496,6 @@ export function getVersion() {
1451
1496
  * @property {string} [insertText] Text to insert (after deleting).
1452
1497
  */
1453
1498
 
1454
- /**
1455
- * RuleOnErrorInfo with all optional properties present.
1456
- *
1457
- * @typedef {Object} RuleOnErrorFixInfoNormalized
1458
- * @property {number} lineNumber Line number (1-based).
1459
- * @property {number} editColumn Column of the fix (1-based).
1460
- * @property {number} deleteCount Count of characters to delete.
1461
- * @property {string} insertText Text to insert (after deleting).
1462
- */
1463
-
1464
1499
  /**
1465
1500
  * Rule definition.
1466
1501
  *
@@ -1506,33 +1541,23 @@ export function getVersion() {
1506
1541
  * @property {Rule[] | Rule} [customRules] Custom rules.
1507
1542
  * @property {string[] | string} [files] Files to lint.
1508
1543
  * @property {RegExp | null} [frontMatter] Front matter pattern.
1509
- * @property {Object} [fs] File system implementation.
1544
+ * @property {FsLike} [fs] File system implementation.
1510
1545
  * @property {boolean} [handleRuleFailures] True to catch exceptions.
1511
1546
  * @property {MarkdownItFactory} [markdownItFactory] Function to create a markdown-it parser.
1512
1547
  * @property {boolean} [noInlineConfig] True to ignore HTML directives.
1513
- * @property {number} [resultVersion] Results object version.
1514
1548
  * @property {Object.<string, string>} [strings] Strings to lint.
1515
1549
  */
1516
1550
 
1517
1551
  /**
1518
1552
  * A markdown-it plugin.
1519
1553
  *
1520
- * @typedef {Array} Plugin
1554
+ * @typedef {Object[]} Plugin
1521
1555
  */
1522
1556
 
1523
1557
  /**
1524
- * Function to pretty-print lint results.
1525
- *
1526
- * @callback ToStringCallback
1527
- * @param {boolean} [ruleAliases] True to use rule aliases.
1528
- * @returns {string} Pretty-printed results.
1529
- */
1530
-
1531
- /**
1532
- * Lint results (for resultVersion 3).
1558
+ * Lint results.
1533
1559
  *
1534
1560
  * @typedef {Object.<string, LintError[]>} LintResults
1535
- * @property {ToStringCallback} toString String representation.
1536
1561
  */
1537
1562
 
1538
1563
  /**
@@ -1542,11 +1567,12 @@ export function getVersion() {
1542
1567
  * @property {number} lineNumber Line number (1-based).
1543
1568
  * @property {string[]} ruleNames Rule name(s).
1544
1569
  * @property {string} ruleDescription Rule description.
1545
- * @property {string} ruleInformation Link to more information.
1546
- * @property {string} errorDetail Detail about the error.
1547
- * @property {string} errorContext Context for the error.
1548
- * @property {number[]} errorRange Column number (1-based) and length.
1549
- * @property {FixInfo} [fixInfo] Fix information.
1570
+ * @property {string | null} ruleInformation Link to more information.
1571
+ * @property {string | null} errorDetail Detail about the error.
1572
+ * @property {string | null} errorContext Context for the error.
1573
+ * @property {number[] | null} errorRange Column number (1-based) and length.
1574
+ * @property {FixInfo | null} fixInfo Fix information.
1575
+ * @property {"error" | "warning"} severity Severity of the error.
1550
1576
  */
1551
1577
 
1552
1578
  /**
@@ -1559,6 +1585,16 @@ export function getVersion() {
1559
1585
  * @property {string} [insertText] Text to insert (after deleting).
1560
1586
  */
1561
1587
 
1588
+ /**
1589
+ * FixInfo with all optional properties present.
1590
+ *
1591
+ * @typedef {Object} FixInfoNormalized
1592
+ * @property {number} lineNumber Line number (1-based).
1593
+ * @property {number} editColumn Column of the fix (1-based).
1594
+ * @property {number} deleteCount Count of characters to delete.
1595
+ * @property {string} insertText Text to insert (after deleting).
1596
+ */
1597
+
1562
1598
  /**
1563
1599
  * Called with the result of linting a string or document.
1564
1600
  *