markdownlint 0.37.4 → 0.39.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 (73) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/CONTRIBUTING.md +2 -3
  3. package/README.md +175 -87
  4. package/doc/Rules.md +273 -47
  5. package/doc/md001.md +14 -0
  6. package/doc/md009.md +8 -9
  7. package/doc/md010.md +6 -0
  8. package/doc/md027.md +8 -0
  9. package/doc/md029.md +2 -0
  10. package/doc/md033.md +7 -1
  11. package/doc/md038.md +23 -11
  12. package/doc/md041.md +31 -16
  13. package/doc/md043.md +22 -4
  14. package/doc/md045.md +9 -1
  15. package/doc/md051.md +17 -0
  16. package/doc/md052.md +12 -0
  17. package/doc/md053.md +3 -3
  18. package/doc/md055.md +1 -1
  19. package/doc/md056.md +1 -1
  20. package/doc/md059.md +33 -0
  21. package/doc/md060.md +70 -0
  22. package/helpers/README.md +2 -2
  23. package/helpers/helpers.cjs +174 -20
  24. package/helpers/micromark-helpers.cjs +37 -7
  25. package/helpers/package.json +1 -1
  26. package/lib/cache.mjs +25 -7
  27. package/lib/configuration-strict.d.ts +1494 -292
  28. package/lib/constants.mjs +6 -5
  29. package/lib/defer-require.cjs +2 -0
  30. package/lib/exports.d.mts +2 -2
  31. package/lib/exports.mjs +2 -2
  32. package/lib/markdownit.cjs +8 -14
  33. package/lib/markdownlint.d.mts +136 -52
  34. package/lib/markdownlint.mjs +469 -409
  35. package/lib/md001.mjs +6 -2
  36. package/lib/md004.mjs +7 -18
  37. package/lib/md007.mjs +2 -1
  38. package/lib/md009.mjs +9 -5
  39. package/lib/md010.mjs +1 -1
  40. package/lib/md022.mjs +5 -2
  41. package/lib/md025.mjs +23 -15
  42. package/lib/md027.mjs +11 -1
  43. package/lib/md029.mjs +25 -14
  44. package/lib/md031.mjs +0 -1
  45. package/lib/md032.mjs +3 -1
  46. package/lib/md033.mjs +28 -20
  47. package/lib/md034.mjs +2 -1
  48. package/lib/md036.mjs +16 -4
  49. package/lib/md037.mjs +10 -7
  50. package/lib/md038.mjs +52 -71
  51. package/lib/md041.mjs +54 -16
  52. package/lib/md042.mjs +4 -2
  53. package/lib/md043.mjs +4 -2
  54. package/lib/md044.mjs +1 -1
  55. package/lib/md045.mjs +3 -1
  56. package/lib/md046.mjs +6 -6
  57. package/lib/md051.mjs +13 -5
  58. package/lib/md052.mjs +2 -1
  59. package/lib/md053.mjs +1 -1
  60. package/lib/md054.mjs +7 -5
  61. package/lib/md055.mjs +7 -6
  62. package/lib/md056.mjs +1 -1
  63. package/lib/md059.mjs +71 -0
  64. package/lib/md060.mjs +121 -0
  65. package/lib/micromark-parse.mjs +17 -3
  66. package/lib/node-imports-browser.mjs +2 -0
  67. package/lib/parse-configuration.mjs +47 -0
  68. package/lib/rules.mjs +5 -1
  69. package/package.json +32 -30
  70. package/schema/.markdownlint.jsonc +97 -62
  71. package/schema/.markdownlint.yaml +86 -57
  72. package/schema/markdownlint-config-schema-strict.json +4713 -1436
  73. package/schema/markdownlint-config-schema.json +4713 -1436
@@ -8,6 +8,7 @@ import { requireMarkdownItCjs } from "./defer-require.cjs";
8
8
  import { resolveModule } from "./resolve-module.cjs";
9
9
  import rules from "./rules.mjs";
10
10
  import { parse as micromarkParse } from "./micromark-parse.mjs";
11
+ import parseConfiguration from "./parse-configuration.mjs";
11
12
  import * as helpers from "../helpers/helpers.cjs";
12
13
 
13
14
  /**
@@ -23,15 +24,17 @@ function validateRuleList(ruleList, synchronous) {
23
24
  // No need to validate if only using built-in rules
24
25
  return result;
25
26
  }
27
+ /** @type {Object.<string, boolean>} */
26
28
  const allIds = {};
27
29
  for (const [ index, rule ] of ruleList.entries()) {
28
30
  const customIndex = index - rules.length;
29
- // eslint-disable-next-line jsdoc/require-jsdoc
30
- 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) {
31
33
  return new Error(
32
34
  `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`);
33
35
  }
34
36
  for (const property of [ "names", "tags" ]) {
37
+ // @ts-ignore
35
38
  const value = rule[property];
36
39
  if (!result &&
37
40
  (!value || !Array.isArray(value) || (value.length === 0) ||
@@ -44,6 +47,7 @@ function validateRuleList(ruleList, synchronous) {
44
47
  [ "function", "function" ]
45
48
  ]) {
46
49
  const property = propertyInfo[0];
50
+ // @ts-ignore
47
51
  const value = rule[property];
48
52
  if (!result && (!value || (typeof value !== propertyInfo[1]))) {
49
53
  result = newError(property, value);
@@ -107,9 +111,17 @@ function validateRuleList(ruleList, synchronous) {
107
111
  * @returns {LintResults} New LintResults instance.
108
112
  */
109
113
  function newResults(ruleList) {
110
- const lintResults = {};
111
- // 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
+ */
112
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} */
113
125
  let ruleNameToRule = null;
114
126
  const results = [];
115
127
  const keys = Object.keys(lintResults);
@@ -120,6 +132,7 @@ function newResults(ruleList) {
120
132
  for (const result of fileResults) {
121
133
  const ruleMoniker = result.ruleNames ?
122
134
  result.ruleNames.join("/") :
135
+ // @ts-ignore
123
136
  (result.ruleName + "/" + result.ruleAlias);
124
137
  results.push(
125
138
  file + ": " +
@@ -160,19 +173,29 @@ function newResults(ruleList) {
160
173
  }
161
174
  return results.join("\n");
162
175
  }
176
+ const lintResults = {};
163
177
  Object.defineProperty(lintResults, "toString", { "value": toString });
164
178
  // @ts-ignore
165
179
  return lintResults;
166
180
  }
167
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
+
168
190
  /**
169
191
  * Remove front matter (if present at beginning of content).
170
192
  *
171
193
  * @param {string} content Markdown content.
172
194
  * @param {RegExp | null} frontMatter Regular expression to match front matter.
173
- * @returns {Object} Trimmed content and front matter lines.
195
+ * @returns {RemoveFrontMatterResult} Trimmed content and front matter lines.
174
196
  */
175
197
  function removeFrontMatter(content, frontMatter) {
198
+ /** @type {string[]} */
176
199
  let frontMatterLines = [];
177
200
  if (frontMatter) {
178
201
  const frontMatterMatch = content.match(frontMatter);
@@ -199,6 +222,7 @@ function removeFrontMatter(content, frontMatter) {
199
222
  * @returns {Object.<string, string[]>} Map of alias to rule name.
200
223
  */
201
224
  function mapAliasToRuleNames(ruleList) {
225
+ /** @type {Object.<string, string[]>} */
202
226
  const aliasToRuleNames = {};
203
227
  // const tagToRuleNames = {};
204
228
  for (const rule of ruleList) {
@@ -228,78 +252,98 @@ function mapAliasToRuleNames(ruleList) {
228
252
  return aliasToRuleNames;
229
253
  }
230
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
+
231
264
  /**
232
265
  * Apply (and normalize) configuration object.
233
266
  *
234
267
  * @param {Rule[]} ruleList List of rules.
235
268
  * @param {Configuration} config Configuration object.
236
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
237
- * names.
238
- * @returns {Configuration} Effective configuration.
269
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
270
+ * @returns {GetEffectiveConfigResult} Effective configuration and rule severities.
239
271
  */
240
272
  function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
241
- const defaultKey = Object.keys(config).filter(
242
- (key) => key.toUpperCase() === "DEFAULT"
243
- );
244
- 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
+ });
245
286
  /** @type {Configuration} */
246
287
  const effectiveConfig = {};
247
- for (const rule of ruleList) {
248
- const ruleName = rule.names[0].toUpperCase();
249
- 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);
250
297
  }
251
298
  // for (const ruleName of deprecatedRuleNames) {
252
299
  // effectiveConfig[ruleName] = false;
253
300
  // }
254
- for (const key of Object.keys(config)) {
255
- 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 = {};
256
308
  if (value) {
257
- if (!(value instanceof Object)) {
258
- 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";
259
322
  }
260
- } else {
261
- value = false;
262
323
  }
263
- const keyUpper = key.toUpperCase();
264
324
  for (const ruleName of (aliasToRuleNames[keyUpper] || [])) {
265
- effectiveConfig[ruleName] = value;
325
+ Object.freeze(effectiveValue);
326
+ effectiveConfig[ruleName] = effectiveValue;
327
+ rulesEnabled.set(ruleName, enabled);
328
+ rulesSeverity.set(ruleName, severity);
266
329
  }
267
330
  }
268
- return effectiveConfig;
331
+ return {
332
+ effectiveConfig,
333
+ rulesEnabled,
334
+ rulesSeverity
335
+ };
269
336
  }
270
337
 
271
338
  /**
272
- * Parse the content of a configuration file.
339
+ * Result object for getEnabledRulesPerLineNumber.
273
340
  *
274
- * @param {string} name Name of the configuration file.
275
- * @param {string} content Configuration content.
276
- * @param {ConfigurationParser[] | null} [parsers] Parsing function(s).
277
- * @returns {Object} Configuration object and error message.
341
+ * @typedef {Object} EnabledRulesPerLineNumberResult
342
+ * @property {Configuration} effectiveConfig Effective configuration.
343
+ * @property {Map<string, boolean>[]} enabledRulesPerLineNumber Enabled rules per line number.
344
+ * @property {Rule[]} enabledRuleList Enabled rule list.
345
+ * @property {Map<string, "error" | "warning">} rulesSeverity Rules severity.
278
346
  */
279
- function parseConfiguration(name, content, parsers) {
280
- let config = null;
281
- let message = "";
282
- const errors = [];
283
- let index = 0;
284
- // Try each parser
285
- (parsers || [ JSON.parse ]).every((parser) => {
286
- try {
287
- config = parser(content);
288
- } catch (error) {
289
- errors.push(`Parser ${index++}: ${error.message}`);
290
- }
291
- return !config;
292
- });
293
- // Message if unable to parse
294
- if (!config) {
295
- errors.unshift(`Unable to parse '${name}'`);
296
- message = errors.join("; ");
297
- }
298
- return {
299
- config,
300
- message
301
- };
302
- }
303
347
 
304
348
  /**
305
349
  * Create a mapping of enabled rules per line.
@@ -309,10 +353,9 @@ function parseConfiguration(name, content, parsers) {
309
353
  * @param {string[]} frontMatterLines List of front matter lines.
310
354
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
311
355
  * @param {Configuration} config Configuration object.
312
- * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
313
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
314
- * names.
315
- * @returns {Object} Effective configuration and enabled rules per line number.
356
+ * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
357
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
358
+ * @returns {EnabledRulesPerLineNumberResult} Effective configuration and enabled rules per line number.
316
359
  */
317
360
  function getEnabledRulesPerLineNumber(
318
361
  ruleList,
@@ -323,13 +366,14 @@ function getEnabledRulesPerLineNumber(
323
366
  configParsers,
324
367
  aliasToRuleNames) {
325
368
  // Shared variables
326
- let enabledRules = {};
327
- let capturedRules = {};
328
- const allRuleNames = [];
369
+ /** @type {Map<string, boolean>} */
370
+ let enabledRules = new Map();
371
+ /** @type {Map<string, boolean>} */
372
+ let capturedRules = enabledRules;
329
373
  const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
330
374
  // Helper functions
331
375
  // eslint-disable-next-line jsdoc/require-jsdoc
332
- 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) {
333
377
  for (const [ lineIndex, line ] of input.entries()) {
334
378
  if (!noInlineConfig) {
335
379
  let match = null;
@@ -350,7 +394,7 @@ function getEnabledRulesPerLineNumber(
350
394
  }
351
395
  }
352
396
  // eslint-disable-next-line jsdoc/require-jsdoc
353
- function configureFile(action, parameter) {
397
+ function configureFile(/** @type {string} */ action, /** @type {string} */ parameter) {
354
398
  if (action === "CONFIGURE-FILE") {
355
399
  const { "config": parsed } = parseConfiguration(
356
400
  "CONFIGURE-FILE", parameter, configParsers
@@ -364,26 +408,27 @@ function getEnabledRulesPerLineNumber(
364
408
  }
365
409
  }
366
410
  // eslint-disable-next-line jsdoc/require-jsdoc
367
- function applyEnableDisable(action, parameter, state) {
368
- state = { ...state };
411
+ function applyEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {Map<string, boolean>} */ state) {
412
+ state = new Map(state);
369
413
  const enabled = (action.startsWith("ENABLE"));
370
414
  const trimmed = parameter && parameter.trim();
415
+ // eslint-disable-next-line no-use-before-define
371
416
  const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames;
372
417
  for (const nameUpper of items) {
373
418
  for (const ruleName of (aliasToRuleNames[nameUpper] || [])) {
374
- state[ruleName] = enabled;
419
+ state.set(ruleName, enabled);
375
420
  }
376
421
  }
377
422
  return state;
378
423
  }
379
424
  // eslint-disable-next-line jsdoc/require-jsdoc
380
- function enableDisableFile(action, parameter) {
425
+ function enableDisableFile(/** @type {string} */ action, /** @type {string} */ parameter) {
381
426
  if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
382
427
  enabledRules = applyEnableDisable(action, parameter, enabledRules);
383
428
  }
384
429
  }
385
430
  // eslint-disable-next-line jsdoc/require-jsdoc
386
- function captureRestoreEnableDisable(action, parameter) {
431
+ function captureRestoreEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter) {
387
432
  if (action === "CAPTURE") {
388
433
  capturedRules = enabledRules;
389
434
  } else if (action === "RESTORE") {
@@ -397,7 +442,7 @@ function getEnabledRulesPerLineNumber(
397
442
  enabledRulesPerLineNumber.push(enabledRules);
398
443
  }
399
444
  // eslint-disable-next-line jsdoc/require-jsdoc
400
- function disableLineNextLine(action, parameter, lineNumber) {
445
+ function disableLineNextLine(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {number} */ lineNumber) {
401
446
  const disableLine = (action === "DISABLE-LINE");
402
447
  const disableNextLine = (action === "DISABLE-NEXT-LINE");
403
448
  if (disableLine || disableNextLine) {
@@ -413,29 +458,24 @@ function getEnabledRulesPerLineNumber(
413
458
  }
414
459
  // Handle inline comments
415
460
  handleInlineConfig([ lines.join("\n") ], configureFile);
416
- const effectiveConfig = getEffectiveConfig(
417
- ruleList, config, aliasToRuleNames);
418
- for (const rule of ruleList) {
419
- const ruleName = rule.names[0].toUpperCase();
420
- allRuleNames.push(ruleName);
421
- enabledRules[ruleName] = !!effectiveConfig[ruleName];
422
- }
461
+ const { effectiveConfig, rulesEnabled, rulesSeverity } = getEffectiveConfig(ruleList, config, aliasToRuleNames);
462
+ const allRuleNames = [ ...rulesEnabled.keys() ];
463
+ enabledRules = new Map(rulesEnabled);
423
464
  capturedRules = enabledRules;
424
465
  handleInlineConfig(lines, enableDisableFile);
425
466
  handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
426
467
  handleInlineConfig(lines, disableLineNextLine);
427
468
  // Create the list of rules that are used at least once
428
- const enabledRuleList = [];
429
- for (const [ index, ruleName ] of allRuleNames.entries()) {
430
- if (enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine[ruleName])) {
431
- enabledRuleList.push(ruleList[index]);
432
- }
433
- }
469
+ const enabledRuleList = ruleList.filter((rule) => {
470
+ const ruleName = rule.names[0].toUpperCase();
471
+ return enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine.get(ruleName));
472
+ });
434
473
  // Return results
435
474
  return {
436
475
  effectiveConfig,
437
476
  enabledRulesPerLineNumber,
438
- enabledRuleList
477
+ enabledRuleList,
478
+ rulesSeverity
439
479
  };
440
480
  }
441
481
 
@@ -443,17 +483,16 @@ function getEnabledRulesPerLineNumber(
443
483
  * Lints a string containing Markdown content.
444
484
  *
445
485
  * @param {Rule[]} ruleList List of rules.
446
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
447
- * names.
486
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
448
487
  * @param {string} name Identifier for the content.
449
488
  * @param {string} content Markdown content.
450
- * @param {Plugin[]} markdownItPlugins Additional plugins.
489
+ * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
451
490
  * @param {Configuration} config Configuration object.
452
- * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
491
+ * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
453
492
  * @param {RegExp | null} frontMatter Regular expression for front matter.
454
493
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
455
494
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
456
- * @param {number} resultVersion Version of the LintResults object to return.
495
+ * @param {boolean} synchronous Whether to execute synchronously.
457
496
  * @param {LintContentCallback} callback Callback (err, result) function.
458
497
  * @returns {void}
459
498
  */
@@ -462,14 +501,17 @@ function lintContent(
462
501
  aliasToRuleNames,
463
502
  name,
464
503
  content,
465
- markdownItPlugins,
504
+ markdownItFactory,
466
505
  config,
467
506
  configParsers,
468
507
  frontMatter,
469
508
  handleRuleFailures,
470
509
  noInlineConfig,
471
- resultVersion,
510
+ synchronous,
472
511
  callback) {
512
+ // Provide a consistent error-reporting callback
513
+ // eslint-disable-next-line jsdoc/reject-any-type
514
+ const callbackError = (/** @type {any} */ error) => callback(error instanceof Error ? error : new Error(error));
473
515
  // Remove UTF-8 byte order marker (if present)
474
516
  content = content.replace(/^\uFEFF/, "");
475
517
  // Remove front matter
@@ -477,7 +519,7 @@ function lintContent(
477
519
  const { frontMatterLines } = removeFrontMatterResult;
478
520
  content = removeFrontMatterResult.content;
479
521
  // Get enabled rules per line (with HTML comments present)
480
- const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList } =
522
+ const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList, rulesSeverity } =
481
523
  getEnabledRulesPerLineNumber(
482
524
  ruleList,
483
525
  content.split(helpers.newLineRe),
@@ -490,263 +532,248 @@ function lintContent(
490
532
  const needMarkdownItTokens = enabledRuleList.some(
491
533
  (rule) => (rule.parser === "markdownit") || (rule.parser === undefined)
492
534
  );
535
+ const needMicromarkTokens = enabledRuleList.some(
536
+ (rule) => (rule.parser === "micromark")
537
+ );
493
538
  const customRulesPresent = (ruleList.length !== rules.length);
494
539
  // Parse content into parser tokens
495
- const micromarkTokens = micromarkParse(
496
- content,
497
- { "freezeTokens": customRulesPresent }
498
- );
540
+ const micromarkTokens = needMicromarkTokens ?
541
+ micromarkParse(content, { "freezeTokens": customRulesPresent }) :
542
+ [];
499
543
  // Hide the content of HTML comments from rules
500
544
  const preClearedContent = content;
501
545
  content = helpers.clearHtmlCommentText(content);
502
546
  // Parse content into lines and get markdown-it tokens
503
547
  const lines = content.split(helpers.newLineRe);
504
- const markdownitTokens = needMarkdownItTokens ?
505
- requireMarkdownItCjs().getMarkdownItTokens(markdownItPlugins, preClearedContent, lines) :
506
- [];
507
- // Create (frozen) parameters for rules
508
- /** @type {MarkdownParsers} */
509
- // @ts-ignore
510
- const parsersMarkdownIt = Object.freeze({
511
- "markdownit": Object.freeze({
512
- "tokens": markdownitTokens
513
- })
514
- });
515
- /** @type {MarkdownParsers} */
516
- // @ts-ignore
517
- const parsersMicromark = Object.freeze({
518
- "micromark": Object.freeze({
519
- "tokens": micromarkTokens
520
- })
521
- });
522
- /** @type {MarkdownParsers} */
523
- // @ts-ignore
524
- const parsersNone = Object.freeze({});
525
- const paramsBase = {
526
- name,
527
- version,
528
- "lines": Object.freeze(lines),
529
- "frontMatterLines": Object.freeze(frontMatterLines)
530
- };
531
- cacheInitialize({
532
- ...paramsBase,
533
- "parsers": parsersMicromark,
534
- "config": null
535
- });
536
- // Function to run for each rule
537
- let results = [];
538
- /**
539
- * @param {Rule} rule Rule.
540
- * @returns {Promise<void> | null} Promise.
541
- */
542
- const forRule = (rule) => {
543
- // Configure rule
544
- const ruleName = rule.names[0].toUpperCase();
545
- const tokens = {};
546
- let parsers = parsersNone;
547
- if (rule.parser === undefined) {
548
- tokens.tokens = markdownitTokens;
549
- parsers = parsersMarkdownIt;
550
- } else if (rule.parser === "markdownit") {
551
- parsers = parsersMarkdownIt;
552
- } else if (rule.parser === "micromark") {
553
- parsers = parsersMicromark;
554
- }
555
- const params = {
556
- ...paramsBase,
557
- ...tokens,
558
- parsers,
559
- "config": effectiveConfig[ruleName]
548
+ // Function to run after fetching markdown-it tokens (when needed)
549
+ const lintContentInternal = (/** @type {MarkdownItToken[]} */ markdownitTokens) => {
550
+ // Create (frozen) parameters for rules
551
+ /** @type {MarkdownParsers} */
552
+ // @ts-ignore
553
+ const parsersMarkdownIt = Object.freeze({
554
+ "markdownit": Object.freeze({
555
+ "tokens": markdownitTokens
556
+ })
557
+ });
558
+ /** @type {MarkdownParsers} */
559
+ // @ts-ignore
560
+ const parsersMicromark = Object.freeze({
561
+ "micromark": Object.freeze({
562
+ "tokens": micromarkTokens
563
+ })
564
+ });
565
+ /** @type {MarkdownParsers} */
566
+ // @ts-ignore
567
+ const parsersNone = Object.freeze({});
568
+ const paramsBase = {
569
+ name,
570
+ version,
571
+ "lines": Object.freeze(lines),
572
+ "frontMatterLines": Object.freeze(frontMatterLines)
560
573
  };
561
- // eslint-disable-next-line jsdoc/require-jsdoc
562
- function throwError(property) {
563
- throw new Error(
564
- `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
565
- }
566
- // eslint-disable-next-line jsdoc/require-jsdoc
567
- function onError(errorInfo) {
568
- if (!errorInfo ||
569
- !helpers.isNumber(errorInfo.lineNumber) ||
570
- (errorInfo.lineNumber < 1) ||
571
- (errorInfo.lineNumber > lines.length)) {
572
- throwError("lineNumber");
573
- }
574
- const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
575
- if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
576
- return;
577
- }
578
- if (errorInfo.detail &&
579
- !helpers.isString(errorInfo.detail)) {
580
- throwError("detail");
581
- }
582
- if (errorInfo.context &&
583
- !helpers.isString(errorInfo.context)) {
584
- throwError("context");
585
- }
586
- if (errorInfo.information &&
587
- !helpers.isUrl(errorInfo.information)) {
588
- throwError("information");
574
+ cacheInitialize({
575
+ ...paramsBase,
576
+ "parsers": parsersMicromark,
577
+ "config": null
578
+ });
579
+ // Function to run for each rule
580
+ /** @type {LintError[]} */
581
+ const results = [];
582
+ /**
583
+ * @param {Rule} rule Rule.
584
+ * @returns {Promise<void> | null} Promise.
585
+ */
586
+ const forRule = (rule) => {
587
+ // Configure rule
588
+ const ruleName = rule.names[0].toUpperCase();
589
+ const tokens = {};
590
+ let parsers = parsersNone;
591
+ if (rule.parser === undefined) {
592
+ tokens.tokens = markdownitTokens;
593
+ parsers = parsersMarkdownIt;
594
+ } else if (rule.parser === "markdownit") {
595
+ parsers = parsersMarkdownIt;
596
+ } else if (rule.parser === "micromark") {
597
+ parsers = parsersMicromark;
589
598
  }
590
- if (errorInfo.range &&
591
- (!Array.isArray(errorInfo.range) ||
592
- (errorInfo.range.length !== 2) ||
593
- !helpers.isNumber(errorInfo.range[0]) ||
594
- (errorInfo.range[0] < 1) ||
595
- !helpers.isNumber(errorInfo.range[1]) ||
596
- (errorInfo.range[1] < 1) ||
597
- ((errorInfo.range[0] + errorInfo.range[1] - 1) >
598
- lines[errorInfo.lineNumber - 1].length))) {
599
- throwError("range");
599
+ const params = Object.freeze({
600
+ ...paramsBase,
601
+ ...tokens,
602
+ parsers,
603
+ /** @type {RuleConfiguration} */
604
+ // @ts-ignore
605
+ "config": effectiveConfig[ruleName]
606
+ });
607
+ // eslint-disable-next-line jsdoc/require-jsdoc
608
+ function throwError(/** @type {string} */ property) {
609
+ throw new Error(
610
+ `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
600
611
  }
601
- const fixInfo = errorInfo.fixInfo;
602
- const cleanFixInfo = {};
603
- if (fixInfo) {
604
- if (!helpers.isObject(fixInfo)) {
605
- throwError("fixInfo");
612
+ // eslint-disable-next-line jsdoc/require-jsdoc
613
+ function onError(/** @type {RuleOnErrorInfo} */ errorInfo) {
614
+ if (!errorInfo ||
615
+ !helpers.isNumber(errorInfo.lineNumber) ||
616
+ (errorInfo.lineNumber < 1) ||
617
+ (errorInfo.lineNumber > lines.length)) {
618
+ throwError("lineNumber");
606
619
  }
607
- if (fixInfo.lineNumber !== undefined) {
608
- if ((!helpers.isNumber(fixInfo.lineNumber) ||
609
- (fixInfo.lineNumber < 1) ||
610
- (fixInfo.lineNumber > lines.length))) {
611
- throwError("fixInfo.lineNumber");
612
- }
613
- cleanFixInfo.lineNumber =
614
- fixInfo.lineNumber + frontMatterLines.length;
620
+ const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
621
+ if (!enabledRulesPerLineNumber[lineNumber].get(ruleName)) {
622
+ return;
615
623
  }
616
- const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
617
- if (fixInfo.editColumn !== undefined) {
618
- if ((!helpers.isNumber(fixInfo.editColumn) ||
619
- (fixInfo.editColumn < 1) ||
620
- (fixInfo.editColumn >
621
- lines[effectiveLineNumber - 1].length + 1))) {
622
- throwError("fixInfo.editColumn");
623
- }
624
- cleanFixInfo.editColumn = fixInfo.editColumn;
624
+ if (errorInfo.detail &&
625
+ !helpers.isString(errorInfo.detail)) {
626
+ throwError("detail");
625
627
  }
626
- if (fixInfo.deleteCount !== undefined) {
627
- if ((!helpers.isNumber(fixInfo.deleteCount) ||
628
- (fixInfo.deleteCount < -1) ||
629
- (fixInfo.deleteCount >
630
- lines[effectiveLineNumber - 1].length))) {
631
- throwError("fixInfo.deleteCount");
632
- }
633
- cleanFixInfo.deleteCount = fixInfo.deleteCount;
628
+ if (errorInfo.context &&
629
+ !helpers.isString(errorInfo.context)) {
630
+ throwError("context");
634
631
  }
635
- if (fixInfo.insertText !== undefined) {
636
- if (!helpers.isString(fixInfo.insertText)) {
637
- throwError("fixInfo.insertText");
632
+ if (errorInfo.information &&
633
+ !helpers.isUrl(errorInfo.information)) {
634
+ throwError("information");
635
+ }
636
+ if (errorInfo.range &&
637
+ (!Array.isArray(errorInfo.range) ||
638
+ (errorInfo.range.length !== 2) ||
639
+ !helpers.isNumber(errorInfo.range[0]) ||
640
+ (errorInfo.range[0] < 1) ||
641
+ !helpers.isNumber(errorInfo.range[1]) ||
642
+ (errorInfo.range[1] < 1) ||
643
+ ((errorInfo.range[0] + errorInfo.range[1] - 1) >
644
+ lines[errorInfo.lineNumber - 1].length))) {
645
+ throwError("range");
646
+ }
647
+ const fixInfo = errorInfo.fixInfo;
648
+ const cleanFixInfo = {};
649
+ if (fixInfo) {
650
+ if (!helpers.isObject(fixInfo)) {
651
+ throwError("fixInfo");
652
+ }
653
+ if (fixInfo.lineNumber !== undefined) {
654
+ if ((!helpers.isNumber(fixInfo.lineNumber) ||
655
+ (fixInfo.lineNumber < 1) ||
656
+ (fixInfo.lineNumber > lines.length))) {
657
+ throwError("fixInfo.lineNumber");
658
+ }
659
+ cleanFixInfo.lineNumber =
660
+ fixInfo.lineNumber + frontMatterLines.length;
661
+ }
662
+ const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
663
+ if (fixInfo.editColumn !== undefined) {
664
+ if ((!helpers.isNumber(fixInfo.editColumn) ||
665
+ (fixInfo.editColumn < 1) ||
666
+ (fixInfo.editColumn >
667
+ lines[effectiveLineNumber - 1].length + 1))) {
668
+ throwError("fixInfo.editColumn");
669
+ }
670
+ cleanFixInfo.editColumn = fixInfo.editColumn;
671
+ }
672
+ if (fixInfo.deleteCount !== undefined) {
673
+ if ((!helpers.isNumber(fixInfo.deleteCount) ||
674
+ (fixInfo.deleteCount < -1) ||
675
+ (fixInfo.deleteCount >
676
+ lines[effectiveLineNumber - 1].length))) {
677
+ throwError("fixInfo.deleteCount");
678
+ }
679
+ cleanFixInfo.deleteCount = fixInfo.deleteCount;
680
+ }
681
+ if (fixInfo.insertText !== undefined) {
682
+ if (!helpers.isString(fixInfo.insertText)) {
683
+ throwError("fixInfo.insertText");
684
+ }
685
+ cleanFixInfo.insertText = fixInfo.insertText;
638
686
  }
639
- cleanFixInfo.insertText = fixInfo.insertText;
640
687
  }
688
+ const information = errorInfo.information || rule.information;
689
+ results.push({
690
+ lineNumber,
691
+ "ruleNames": rule.names,
692
+ "ruleDescription": rule.description,
693
+ "ruleInformation": information ? information.href : null,
694
+ "errorDetail": errorInfo.detail?.replace(helpers.newLineRe, " ") || null,
695
+ "errorContext": errorInfo.context?.replace(helpers.newLineRe, " ") || null,
696
+ "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
697
+ "fixInfo": fixInfo ? cleanFixInfo : null,
698
+ // @ts-ignore
699
+ "severity": rulesSeverity.get(ruleName)
700
+ });
641
701
  }
642
- const information = errorInfo.information || rule.information;
643
- results.push({
644
- lineNumber,
645
- "ruleName": rule.names[0],
646
- "ruleNames": rule.names,
647
- "ruleDescription": rule.description,
648
- "ruleInformation": information ? information.href : null,
649
- "errorDetail": errorInfo.detail || null,
650
- "errorContext": errorInfo.context || null,
651
- "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
652
- "fixInfo": fixInfo ? cleanFixInfo : null
702
+ // Call (possibly external) rule function to report errors
703
+ // @ts-ignore
704
+ const catchCallsOnError = (error) => onError({
705
+ "lineNumber": 1,
706
+ "detail": `This rule threw an exception: ${error.message || error}`
653
707
  });
654
- }
655
- // Call (possibly external) rule function to report errors
656
- const catchCallsOnError = (error) => onError({
657
- "lineNumber": 1,
658
- "detail": `This rule threw an exception: ${error.message || error}`
659
- });
660
- const invokeRuleFunction = () => rule.function(params, onError);
661
- if (rule.asynchronous) {
662
- // Asynchronous rule, ensure it returns a Promise
663
- const ruleFunctionPromise =
664
- Promise.resolve().then(invokeRuleFunction);
665
- return handleRuleFailures ?
666
- ruleFunctionPromise.catch(catchCallsOnError) :
667
- ruleFunctionPromise;
668
- }
669
- // Synchronous rule
708
+ const invokeRuleFunction = () => rule.function(params, onError);
709
+ if (rule.asynchronous) {
710
+ // Asynchronous rule, ensure it returns a Promise
711
+ const ruleFunctionPromise =
712
+ Promise.resolve().then(invokeRuleFunction);
713
+ return handleRuleFailures ?
714
+ ruleFunctionPromise.catch(catchCallsOnError) :
715
+ ruleFunctionPromise;
716
+ }
717
+ // Synchronous rule
718
+ try {
719
+ invokeRuleFunction();
720
+ } catch(error) {
721
+ if (handleRuleFailures) {
722
+ catchCallsOnError(error);
723
+ } else {
724
+ throw error;
725
+ }
726
+ }
727
+ return null;
728
+ };
729
+ const formatResults = () => {
730
+ // Sort results by rule name by line number
731
+ results.sort((a, b) => (
732
+ a.ruleNames[0].localeCompare(b.ruleNames[0]) ||
733
+ a.lineNumber - b.lineNumber
734
+ ));
735
+ return results;
736
+ };
737
+ // Run all rules
738
+ const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
739
+ const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
740
+ const ruleListAsyncFirst = [
741
+ ...ruleListAsync,
742
+ ...ruleListSync
743
+ ];
744
+ const callbackSuccess = () => callback(null, formatResults());
670
745
  try {
671
- invokeRuleFunction();
672
- } catch (error) {
673
- if (handleRuleFailures) {
674
- catchCallsOnError(error);
746
+ const ruleResults = ruleListAsyncFirst.map(forRule);
747
+ if (ruleListAsync.length > 0) {
748
+ Promise.all(ruleResults.slice(0, ruleListAsync.length))
749
+ .then(callbackSuccess)
750
+ .catch(callbackError);
675
751
  } else {
676
- throw error;
752
+ callbackSuccess();
677
753
  }
754
+ } catch(error) {
755
+ callbackError(error);
756
+ } finally {
757
+ cacheInitialize();
678
758
  }
679
- return null;
680
759
  };
681
- // eslint-disable-next-line jsdoc/require-jsdoc
682
- function formatResults() {
683
- // Sort results by rule name by line number
684
- results.sort((a, b) => (
685
- a.ruleName.localeCompare(b.ruleName) ||
686
- a.lineNumber - b.lineNumber
687
- ));
688
- if (resultVersion < 3) {
689
- // Remove fixInfo and multiple errors for the same rule and line number
690
- const noPrevious = {
691
- "ruleName": null,
692
- "lineNumber": -1
693
- };
694
- results = results.filter((error, index, array) => {
695
- delete error.fixInfo;
696
- const previous = array[index - 1] || noPrevious;
697
- return (
698
- (error.ruleName !== previous.ruleName) ||
699
- (error.lineNumber !== previous.lineNumber)
700
- );
701
- });
702
- }
703
- if (resultVersion === 0) {
704
- // Return a dictionary of rule->[line numbers]
705
- const dictionary = {};
706
- for (const error of results) {
707
- const ruleLines = dictionary[error.ruleName] || [];
708
- ruleLines.push(error.lineNumber);
709
- dictionary[error.ruleName] = ruleLines;
710
- }
711
- // @ts-ignore
712
- results = dictionary;
713
- } else if (resultVersion === 1) {
714
- // Use ruleAlias instead of ruleNames
715
- for (const error of results) {
716
- error.ruleAlias = error.ruleNames[1] || error.ruleName;
717
- delete error.ruleNames;
718
- }
719
- } else {
720
- // resultVersion 2 or 3: Remove unwanted ruleName
721
- for (const error of results) {
722
- delete error.ruleName;
723
- }
724
- }
725
- return results;
726
- }
727
- // Run all rules
728
- const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
729
- const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
730
- const ruleListAsyncFirst = [
731
- ...ruleListAsync,
732
- ...ruleListSync
733
- ];
734
- const callbackSuccess = () => callback(null, formatResults());
735
- const callbackError =
736
- (error) => callback(error instanceof Error ? error : new Error(error));
737
- try {
738
- const ruleResults = ruleListAsyncFirst.map(forRule);
739
- if (ruleListAsync.length > 0) {
740
- Promise.all(ruleResults.slice(0, ruleListAsync.length))
741
- .then(callbackSuccess)
742
- .catch(callbackError);
743
- } else {
744
- callbackSuccess();
745
- }
746
- } catch (error) {
747
- callbackError(error);
748
- } finally {
749
- cacheInitialize();
760
+ if (!needMarkdownItTokens || synchronous) {
761
+ // Need/able to call into markdown-it and lintContentInternal synchronously
762
+ const markdownItTokens = needMarkdownItTokens ?
763
+ requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), preClearedContent, lines) :
764
+ [];
765
+ lintContentInternal(markdownItTokens);
766
+ } else {
767
+ // Need to call into markdown-it and lintContentInternal asynchronously
768
+ Promise.all([
769
+ // eslint-disable-next-line no-inline-comments
770
+ import(/* webpackMode: "eager" */ "./markdownit.cjs"),
771
+ // eslint-disable-next-line no-promise-executor-return
772
+ new Promise((resolve) => resolve(markdownItFactory()))
773
+ ]).then(([ markdownitCjs, markdownIt ]) => {
774
+ const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, preClearedContent, lines);
775
+ lintContentInternal(markdownItTokens);
776
+ }).catch(callbackError);
750
777
  }
751
778
  }
752
779
 
@@ -754,17 +781,15 @@ function lintContent(
754
781
  * Lints a file containing Markdown content.
755
782
  *
756
783
  * @param {Rule[]} ruleList List of rules.
757
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
758
- * names.
784
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
759
785
  * @param {string} file Path of file to lint.
760
- * @param {Plugin[]} markdownItPlugins Additional plugins.
786
+ * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
761
787
  * @param {Configuration} config Configuration object.
762
- * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
788
+ * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
763
789
  * @param {RegExp | null} frontMatter Regular expression for front matter.
764
790
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
765
791
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
766
- * @param {number} resultVersion Version of the LintResults object to return.
767
- * @param {Object} fs File system implementation.
792
+ * @param {FsLike} fs File system implementation.
768
793
  * @param {boolean} synchronous Whether to execute synchronously.
769
794
  * @param {LintContentCallback} callback Callback (err, result) function.
770
795
  * @returns {void}
@@ -773,18 +798,17 @@ function lintFile(
773
798
  ruleList,
774
799
  aliasToRuleNames,
775
800
  file,
776
- markdownItPlugins,
801
+ markdownItFactory,
777
802
  config,
778
803
  configParsers,
779
804
  frontMatter,
780
805
  handleRuleFailures,
781
806
  noInlineConfig,
782
- resultVersion,
783
807
  fs,
784
808
  synchronous,
785
809
  callback) {
786
810
  // eslint-disable-next-line jsdoc/require-jsdoc
787
- function lintContentWrapper(err, content) {
811
+ function lintContentWrapper(/** @type {Error | null} */ err, /** @type {string} */ content) {
788
812
  if (err) {
789
813
  return callback(err);
790
814
  }
@@ -793,13 +817,13 @@ function lintFile(
793
817
  aliasToRuleNames,
794
818
  file,
795
819
  content,
796
- markdownItPlugins,
820
+ markdownItFactory,
797
821
  config,
798
822
  configParsers,
799
823
  frontMatter,
800
824
  handleRuleFailures,
801
825
  noInlineConfig,
802
- resultVersion,
826
+ synchronous,
803
827
  callback
804
828
  );
805
829
  }
@@ -823,6 +847,8 @@ function lintInput(options, synchronous, callback) {
823
847
  // Normalize inputs
824
848
  options = options || {};
825
849
  callback = callback || function noop() {};
850
+ /** @type {Rule[]} */
851
+ // @ts-ignore
826
852
  const customRuleList =
827
853
  [ options.customRules || [] ]
828
854
  .flat()
@@ -842,6 +868,7 @@ function lintInput(options, synchronous, callback) {
842
868
  callback(ruleErr);
843
869
  return;
844
870
  }
871
+ /** @type {string[]} */
845
872
  let files = [];
846
873
  if (Array.isArray(options.files)) {
847
874
  files = [ ...options.files ];
@@ -851,16 +878,20 @@ function lintInput(options, synchronous, callback) {
851
878
  const strings = options.strings || {};
852
879
  const stringsKeys = Object.keys(strings);
853
880
  const config = options.config || { "default": true };
854
- const configParsers = options.configParsers || null;
881
+ const configParsers = options.configParsers || undefined;
855
882
  const frontMatter = (options.frontMatter === undefined) ?
856
883
  helpers.frontMatterRe :
857
884
  options.frontMatter;
858
885
  const handleRuleFailures = !!options.handleRuleFailures;
859
886
  const noInlineConfig = !!options.noInlineConfig;
860
- const resultVersion = (options.resultVersion === undefined) ?
861
- 3 :
862
- options.resultVersion;
863
- const markdownItPlugins = options.markdownItPlugins || [];
887
+ // @ts-ignore
888
+ // eslint-disable-next-line dot-notation
889
+ const resultVersion = (options["resultVersion"] === undefined) ? 3 : options["resultVersion"];
890
+ const markdownItFactory =
891
+ options.markdownItFactory ||
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
864
895
  const fs = options.fs || nodeFs;
865
896
  const aliasToRuleNames = mapAliasToRuleNames(ruleList);
866
897
  const results = newResults(ruleList);
@@ -868,14 +899,16 @@ function lintInput(options, synchronous, callback) {
868
899
  let concurrency = 0;
869
900
  // eslint-disable-next-line jsdoc/require-jsdoc
870
901
  function lintWorker() {
871
- let currentItem = null;
902
+ /** @type {string | undefined} */
903
+ let currentItem = undefined;
872
904
  // eslint-disable-next-line jsdoc/require-jsdoc
873
- function lintWorkerCallback(err, result) {
905
+ function lintWorkerCallback(/** @type {Error | null} */ err, /** @type {LintError[] | undefined} */ result) {
874
906
  concurrency--;
875
907
  if (err) {
876
908
  done = true;
877
909
  return callback(err);
878
910
  }
911
+ // @ts-ignore
879
912
  results[currentItem] = result;
880
913
  if (!synchronous) {
881
914
  lintWorker();
@@ -884,21 +917,19 @@ function lintInput(options, synchronous, callback) {
884
917
  }
885
918
  if (done) {
886
919
  // Abort for error or nothing left to do
887
- } else if (files.length > 0) {
920
+ } else if ((currentItem = files.shift())) {
888
921
  // Lint next file
889
922
  concurrency++;
890
- currentItem = files.shift();
891
923
  lintFile(
892
924
  ruleList,
893
925
  aliasToRuleNames,
894
926
  currentItem,
895
- markdownItPlugins,
927
+ markdownItFactory,
896
928
  config,
897
929
  configParsers,
898
930
  frontMatter,
899
931
  handleRuleFailures,
900
932
  noInlineConfig,
901
- resultVersion,
902
933
  fs,
903
934
  synchronous,
904
935
  lintWorkerCallback
@@ -911,19 +942,28 @@ function lintInput(options, synchronous, callback) {
911
942
  aliasToRuleNames,
912
943
  currentItem,
913
944
  strings[currentItem] || "",
914
- markdownItPlugins,
945
+ markdownItFactory,
915
946
  config,
916
947
  configParsers,
917
948
  frontMatter,
918
949
  handleRuleFailures,
919
950
  noInlineConfig,
920
- resultVersion,
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,19 +1230,20 @@ 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) {
1246
+ // @ts-ignore
1197
1247
  throw new Error(message);
1198
1248
  }
1199
1249
  // Extend configuration
@@ -1203,7 +1253,7 @@ export function readConfigSync(file, parsers, fs) {
1203
1253
  const resolvedExtends = resolveConfigExtendsSync(
1204
1254
  file,
1205
1255
  helpers.expandTildePath(configExtends, os),
1206
- fs
1256
+ fsLike
1207
1257
  );
1208
1258
  return {
1209
1259
  ...readConfigSync(resolvedExtends, parsers, fs),
@@ -1216,9 +1266,9 @@ export function readConfigSync(file, parsers, fs) {
1216
1266
  /**
1217
1267
  * Normalizes the fields of a RuleOnErrorFixInfo instance.
1218
1268
  *
1219
- * @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
1269
+ * @param {FixInfo} fixInfo RuleOnErrorFixInfo instance.
1220
1270
  * @param {number} [lineNumber] Line number.
1221
- * @returns {RuleOnErrorFixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
1271
+ * @returns {FixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
1222
1272
  */
1223
1273
  function normalizeFixInfo(fixInfo, lineNumber = 0) {
1224
1274
  return {
@@ -1233,7 +1283,7 @@ function normalizeFixInfo(fixInfo, lineNumber = 0) {
1233
1283
  * Applies the specified fix to a Markdown content line.
1234
1284
  *
1235
1285
  * @param {string} line Line of Markdown content.
1236
- * @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
1286
+ * @param {FixInfo} fixInfo FixInfo instance.
1237
1287
  * @param {string} [lineEnding] Line ending to use.
1238
1288
  * @returns {string | null} Fixed content or null if deleted.
1239
1289
  */
@@ -1249,7 +1299,7 @@ export function applyFix(line, fixInfo, lineEnding = "\n") {
1249
1299
  * Applies as many of the specified fixes as possible to Markdown content.
1250
1300
  *
1251
1301
  * @param {string} input Lines of Markdown content.
1252
- * @param {RuleOnErrorInfo[]} errors RuleOnErrorInfo instances.
1302
+ * @param {LintError[]} errors LintError instances.
1253
1303
  * @returns {string} Fixed content.
1254
1304
  */
1255
1305
  export function applyFixes(input, errors) {
@@ -1345,8 +1395,6 @@ export function getVersion() {
1345
1395
  * @returns {void}
1346
1396
  */
1347
1397
 
1348
- /* eslint-disable jsdoc/valid-types */
1349
-
1350
1398
  /**
1351
1399
  * Rule parameters.
1352
1400
  *
@@ -1359,8 +1407,6 @@ export function getVersion() {
1359
1407
  * @property {string} version Version of the markdownlint library.
1360
1408
  */
1361
1409
 
1362
- /* eslint-enable jsdoc/valid-types */
1363
-
1364
1410
  /**
1365
1411
  * Markdown parser data.
1366
1412
  *
@@ -1450,16 +1496,6 @@ export function getVersion() {
1450
1496
  * @property {string} [insertText] Text to insert (after deleting).
1451
1497
  */
1452
1498
 
1453
- /**
1454
- * RuleOnErrorInfo with all optional properties present.
1455
- *
1456
- * @typedef {Object} RuleOnErrorFixInfoNormalized
1457
- * @property {number} lineNumber Line number (1-based).
1458
- * @property {number} editColumn Column of the fix (1-based).
1459
- * @property {number} deleteCount Count of characters to delete.
1460
- * @property {string} insertText Text to insert (after deleting).
1461
- */
1462
-
1463
1499
  /**
1464
1500
  * Rule definition.
1465
1501
  *
@@ -1473,6 +1509,29 @@ export function getVersion() {
1473
1509
  * @property {RuleFunction} function Rule implementation.
1474
1510
  */
1475
1511
 
1512
+ /**
1513
+ * Method used by the markdown-it parser to parse input.
1514
+ *
1515
+ * @callback MarkdownItParse
1516
+ * @param {string} src Source string.
1517
+ * @param {Object} env Environment sandbox.
1518
+ * @returns {import("markdown-it").Token[]} Tokens.
1519
+ */
1520
+
1521
+ /**
1522
+ * Instance of the markdown-it parser.
1523
+ *
1524
+ * @typedef MarkdownIt
1525
+ * @property {MarkdownItParse} parse Method to parse input.
1526
+ */
1527
+
1528
+ /**
1529
+ * Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
1530
+ *
1531
+ * @callback MarkdownItFactory
1532
+ * @returns {MarkdownIt|Promise<MarkdownIt>} Instance of the markdown-it parser.
1533
+ */
1534
+
1476
1535
  /**
1477
1536
  * Configuration options.
1478
1537
  *
@@ -1482,33 +1541,23 @@ export function getVersion() {
1482
1541
  * @property {Rule[] | Rule} [customRules] Custom rules.
1483
1542
  * @property {string[] | string} [files] Files to lint.
1484
1543
  * @property {RegExp | null} [frontMatter] Front matter pattern.
1485
- * @property {Object} [fs] File system implementation.
1544
+ * @property {FsLike} [fs] File system implementation.
1486
1545
  * @property {boolean} [handleRuleFailures] True to catch exceptions.
1487
- * @property {Plugin[]} [markdownItPlugins] Additional plugins.
1546
+ * @property {MarkdownItFactory} [markdownItFactory] Function to create a markdown-it parser.
1488
1547
  * @property {boolean} [noInlineConfig] True to ignore HTML directives.
1489
- * @property {number} [resultVersion] Results object version.
1490
1548
  * @property {Object.<string, string>} [strings] Strings to lint.
1491
1549
  */
1492
1550
 
1493
1551
  /**
1494
1552
  * A markdown-it plugin.
1495
1553
  *
1496
- * @typedef {Array} Plugin
1554
+ * @typedef {Object[]} Plugin
1497
1555
  */
1498
1556
 
1499
1557
  /**
1500
- * Function to pretty-print lint results.
1501
- *
1502
- * @callback ToStringCallback
1503
- * @param {boolean} [ruleAliases] True to use rule aliases.
1504
- * @returns {string}
1505
- */
1506
-
1507
- /**
1508
- * Lint results (for resultVersion 3).
1558
+ * Lint results.
1509
1559
  *
1510
1560
  * @typedef {Object.<string, LintError[]>} LintResults
1511
- * @property {ToStringCallback} toString String representation.
1512
1561
  */
1513
1562
 
1514
1563
  /**
@@ -1518,11 +1567,12 @@ export function getVersion() {
1518
1567
  * @property {number} lineNumber Line number (1-based).
1519
1568
  * @property {string[]} ruleNames Rule name(s).
1520
1569
  * @property {string} ruleDescription Rule description.
1521
- * @property {string} ruleInformation Link to more information.
1522
- * @property {string} errorDetail Detail about the error.
1523
- * @property {string} errorContext Context for the error.
1524
- * @property {number[]} errorRange Column number (1-based) and length.
1525
- * @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.
1526
1576
  */
1527
1577
 
1528
1578
  /**
@@ -1535,6 +1585,16 @@ export function getVersion() {
1535
1585
  * @property {string} [insertText] Text to insert (after deleting).
1536
1586
  */
1537
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
+
1538
1598
  /**
1539
1599
  * Called with the result of linting a string or document.
1540
1600
  *