markdownlint 0.37.3 → 0.38.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 (50) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/CONTRIBUTING.md +2 -3
  3. package/README.md +39 -21
  4. package/doc/Rules.md +158 -35
  5. package/doc/md027.md +8 -0
  6. package/doc/md038.md +23 -11
  7. package/doc/md041.md +31 -16
  8. package/doc/md043.md +22 -4
  9. package/doc/md045.md +9 -1
  10. package/doc/md051.md +17 -0
  11. package/doc/md052.md +12 -0
  12. package/doc/md053.md +3 -3
  13. package/doc/md059.md +30 -0
  14. package/helpers/README.md +2 -2
  15. package/helpers/helpers.cjs +4 -6
  16. package/helpers/micromark-helpers.cjs +29 -3
  17. package/helpers/package.json +1 -1
  18. package/lib/cache.mjs +19 -5
  19. package/lib/configuration-strict.d.ts +190 -126
  20. package/lib/constants.mjs +1 -1
  21. package/lib/defer-require.cjs +16 -0
  22. package/lib/exports.d.mts +2 -0
  23. package/lib/exports.mjs +2 -0
  24. package/lib/markdownit.cjs +6 -13
  25. package/lib/markdownlint.d.mts +36 -2
  26. package/lib/markdownlint.mjs +314 -292
  27. package/lib/md025.mjs +23 -15
  28. package/lib/md027.mjs +11 -1
  29. package/lib/md036.mjs +12 -2
  30. package/lib/md038.mjs +52 -71
  31. package/lib/md039.mjs +3 -22
  32. package/lib/md041.mjs +54 -16
  33. package/lib/md043.mjs +2 -0
  34. package/lib/md045.mjs +3 -1
  35. package/lib/md051.mjs +13 -5
  36. package/lib/md052.mjs +2 -1
  37. package/lib/md059.mjs +71 -0
  38. package/lib/micromark-parse.mjs +14 -3
  39. package/lib/node-imports-browser.mjs +0 -2
  40. package/lib/node-imports-node.mjs +0 -3
  41. package/lib/parse-configuration.mjs +46 -0
  42. package/lib/resolve-module.cjs +52 -0
  43. package/lib/resolve-module.d.cts +34 -0
  44. package/lib/rules.mjs +3 -1
  45. package/package.json +30 -27
  46. package/schema/.markdownlint.jsonc +82 -60
  47. package/schema/.markdownlint.yaml +74 -56
  48. package/schema/markdownlint-config-schema-strict.json +259 -144
  49. package/schema/markdownlint-config-schema.json +259 -144
  50. package/lib/node-imports-browser-module.cjs +0 -7
@@ -1,12 +1,14 @@
1
1
  // @ts-check
2
2
 
3
3
  // @ts-ignore
4
- import { fs as nodeFs, module, os, path } from "#node-imports";
5
- const dynamicRequire = module.createRequire(import.meta.url);
4
+ import { fs as nodeFs, os, path } from "#node-imports";
6
5
  import { initialize as cacheInitialize } from "./cache.mjs";
7
6
  import { version } from "./constants.mjs";
7
+ import { requireMarkdownItCjs } from "./defer-require.cjs";
8
+ import { resolveModule } from "./resolve-module.cjs";
8
9
  import rules from "./rules.mjs";
9
10
  import { parse as micromarkParse } from "./micromark-parse.mjs";
11
+ import parseConfiguration from "./parse-configuration.mjs";
10
12
  import * as helpers from "../helpers/helpers.cjs";
11
13
 
12
14
  /**
@@ -268,37 +270,13 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
268
270
  }
269
271
 
270
272
  /**
271
- * Parse the content of a configuration file.
273
+ * Result object for getEnabledRulesPerLineNumber.
272
274
  *
273
- * @param {string} name Name of the configuration file.
274
- * @param {string} content Configuration content.
275
- * @param {ConfigurationParser[] | null} [parsers] Parsing function(s).
276
- * @returns {Object} Configuration object and error message.
275
+ * @typedef {Object} EnabledRulesPerLineNumberResult
276
+ * @property {Configuration} effectiveConfig Effective configuration.
277
+ * @property {any[]} enabledRulesPerLineNumber Enabled rules per line number.
278
+ * @property {Rule[]} enabledRuleList Enabled rule list.
277
279
  */
278
- function parseConfiguration(name, content, parsers) {
279
- let config = null;
280
- let message = "";
281
- const errors = [];
282
- let index = 0;
283
- // Try each parser
284
- (parsers || [ JSON.parse ]).every((parser) => {
285
- try {
286
- config = parser(content);
287
- } catch (error) {
288
- errors.push(`Parser ${index++}: ${error.message}`);
289
- }
290
- return !config;
291
- });
292
- // Message if unable to parse
293
- if (!config) {
294
- errors.unshift(`Unable to parse '${name}'`);
295
- message = errors.join("; ");
296
- }
297
- return {
298
- config,
299
- message
300
- };
301
- }
302
280
 
303
281
  /**
304
282
  * Create a mapping of enabled rules per line.
@@ -308,10 +286,10 @@ function parseConfiguration(name, content, parsers) {
308
286
  * @param {string[]} frontMatterLines List of front matter lines.
309
287
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
310
288
  * @param {Configuration} config Configuration object.
311
- * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
289
+ * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
312
290
  * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
313
291
  * names.
314
- * @returns {Object} Effective configuration and enabled rules per line number.
292
+ * @returns {EnabledRulesPerLineNumberResult} Effective configuration and enabled rules per line number.
315
293
  */
316
294
  function getEnabledRulesPerLineNumber(
317
295
  ruleList,
@@ -442,17 +420,17 @@ function getEnabledRulesPerLineNumber(
442
420
  * Lints a string containing Markdown content.
443
421
  *
444
422
  * @param {Rule[]} ruleList List of rules.
445
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
446
- * names.
423
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
447
424
  * @param {string} name Identifier for the content.
448
425
  * @param {string} content Markdown content.
449
- * @param {Plugin[]} markdownItPlugins Additional plugins.
426
+ * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
450
427
  * @param {Configuration} config Configuration object.
451
- * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
428
+ * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
452
429
  * @param {RegExp | null} frontMatter Regular expression for front matter.
453
430
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
454
431
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
455
432
  * @param {number} resultVersion Version of the LintResults object to return.
433
+ * @param {boolean} synchronous Whether to execute synchronously.
456
434
  * @param {LintContentCallback} callback Callback (err, result) function.
457
435
  * @returns {void}
458
436
  */
@@ -461,14 +439,17 @@ function lintContent(
461
439
  aliasToRuleNames,
462
440
  name,
463
441
  content,
464
- markdownItPlugins,
442
+ markdownItFactory,
465
443
  config,
466
444
  configParsers,
467
445
  frontMatter,
468
446
  handleRuleFailures,
469
447
  noInlineConfig,
470
448
  resultVersion,
449
+ synchronous,
471
450
  callback) {
451
+ // Provide a consistent error-reporting callback
452
+ const callbackError = (error) => callback(error instanceof Error ? error : new Error(error));
472
453
  // Remove UTF-8 byte order marker (if present)
473
454
  content = content.replace(/^\uFEFF/, "");
474
455
  // Remove front matter
@@ -489,263 +470,280 @@ function lintContent(
489
470
  const needMarkdownItTokens = enabledRuleList.some(
490
471
  (rule) => (rule.parser === "markdownit") || (rule.parser === undefined)
491
472
  );
473
+ const needMicromarkTokens = enabledRuleList.some(
474
+ (rule) => (rule.parser === "micromark")
475
+ );
492
476
  const customRulesPresent = (ruleList.length !== rules.length);
493
477
  // Parse content into parser tokens
494
- const micromarkTokens = micromarkParse(
495
- content,
496
- { "freezeTokens": customRulesPresent }
497
- );
478
+ const micromarkTokens = needMicromarkTokens ?
479
+ micromarkParse(content, { "freezeTokens": customRulesPresent }) :
480
+ [];
498
481
  // Hide the content of HTML comments from rules
499
482
  const preClearedContent = content;
500
483
  content = helpers.clearHtmlCommentText(content);
501
484
  // Parse content into lines and get markdown-it tokens
502
485
  const lines = content.split(helpers.newLineRe);
503
- const markdownitTokens = needMarkdownItTokens ?
504
- dynamicRequire("./markdownit.cjs").getMarkdownItTokens(markdownItPlugins, preClearedContent, lines) :
505
- [];
506
- // Create (frozen) parameters for rules
507
- /** @type {MarkdownParsers} */
508
- // @ts-ignore
509
- const parsersMarkdownIt = Object.freeze({
510
- "markdownit": Object.freeze({
511
- "tokens": markdownitTokens
512
- })
513
- });
514
- /** @type {MarkdownParsers} */
515
- // @ts-ignore
516
- const parsersMicromark = Object.freeze({
517
- "micromark": Object.freeze({
518
- "tokens": micromarkTokens
519
- })
520
- });
521
- /** @type {MarkdownParsers} */
522
- // @ts-ignore
523
- const parsersNone = Object.freeze({});
524
- const paramsBase = {
525
- name,
526
- version,
527
- "lines": Object.freeze(lines),
528
- "frontMatterLines": Object.freeze(frontMatterLines)
529
- };
530
- cacheInitialize({
531
- ...paramsBase,
532
- "parsers": parsersMicromark,
533
- "config": null
534
- });
535
- // Function to run for each rule
536
- let results = [];
537
- /**
538
- * @param {Rule} rule Rule.
539
- * @returns {Promise<void> | null} Promise.
540
- */
541
- const forRule = (rule) => {
542
- // Configure rule
543
- const ruleName = rule.names[0].toUpperCase();
544
- const tokens = {};
545
- let parsers = parsersNone;
546
- if (rule.parser === undefined) {
547
- tokens.tokens = markdownitTokens;
548
- parsers = parsersMarkdownIt;
549
- } else if (rule.parser === "markdownit") {
550
- parsers = parsersMarkdownIt;
551
- } else if (rule.parser === "micromark") {
552
- parsers = parsersMicromark;
553
- }
554
- const params = {
555
- ...paramsBase,
556
- ...tokens,
557
- parsers,
558
- "config": effectiveConfig[ruleName]
486
+ // Function to run after fetching markdown-it tokens (when needed)
487
+ const lintContentInternal = (markdownitTokens) => {
488
+ // Create (frozen) parameters for rules
489
+ /** @type {MarkdownParsers} */
490
+ // @ts-ignore
491
+ const parsersMarkdownIt = Object.freeze({
492
+ "markdownit": Object.freeze({
493
+ "tokens": markdownitTokens
494
+ })
495
+ });
496
+ /** @type {MarkdownParsers} */
497
+ // @ts-ignore
498
+ const parsersMicromark = Object.freeze({
499
+ "micromark": Object.freeze({
500
+ "tokens": micromarkTokens
501
+ })
502
+ });
503
+ /** @type {MarkdownParsers} */
504
+ // @ts-ignore
505
+ const parsersNone = Object.freeze({});
506
+ const paramsBase = {
507
+ name,
508
+ version,
509
+ "lines": Object.freeze(lines),
510
+ "frontMatterLines": Object.freeze(frontMatterLines)
559
511
  };
560
- // eslint-disable-next-line jsdoc/require-jsdoc
561
- function throwError(property) {
562
- throw new Error(
563
- `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
564
- }
565
- // eslint-disable-next-line jsdoc/require-jsdoc
566
- function onError(errorInfo) {
567
- if (!errorInfo ||
568
- !helpers.isNumber(errorInfo.lineNumber) ||
569
- (errorInfo.lineNumber < 1) ||
570
- (errorInfo.lineNumber > lines.length)) {
571
- throwError("lineNumber");
572
- }
573
- const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
574
- if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
575
- return;
576
- }
577
- if (errorInfo.detail &&
578
- !helpers.isString(errorInfo.detail)) {
579
- throwError("detail");
580
- }
581
- if (errorInfo.context &&
582
- !helpers.isString(errorInfo.context)) {
583
- throwError("context");
584
- }
585
- if (errorInfo.information &&
586
- !helpers.isUrl(errorInfo.information)) {
587
- throwError("information");
512
+ cacheInitialize({
513
+ ...paramsBase,
514
+ "parsers": parsersMicromark,
515
+ "config": null
516
+ });
517
+ // Function to run for each rule
518
+ let results = [];
519
+ /**
520
+ * @param {Rule} rule Rule.
521
+ * @returns {Promise<void> | null} Promise.
522
+ */
523
+ const forRule = (rule) => {
524
+ // Configure rule
525
+ const ruleName = rule.names[0].toUpperCase();
526
+ const tokens = {};
527
+ let parsers = parsersNone;
528
+ if (rule.parser === undefined) {
529
+ tokens.tokens = markdownitTokens;
530
+ parsers = parsersMarkdownIt;
531
+ } else if (rule.parser === "markdownit") {
532
+ parsers = parsersMarkdownIt;
533
+ } else if (rule.parser === "micromark") {
534
+ parsers = parsersMicromark;
588
535
  }
589
- if (errorInfo.range &&
590
- (!Array.isArray(errorInfo.range) ||
591
- (errorInfo.range.length !== 2) ||
592
- !helpers.isNumber(errorInfo.range[0]) ||
593
- (errorInfo.range[0] < 1) ||
594
- !helpers.isNumber(errorInfo.range[1]) ||
595
- (errorInfo.range[1] < 1) ||
596
- ((errorInfo.range[0] + errorInfo.range[1] - 1) >
597
- lines[errorInfo.lineNumber - 1].length))) {
598
- throwError("range");
536
+ const params = {
537
+ ...paramsBase,
538
+ ...tokens,
539
+ parsers,
540
+ "config": effectiveConfig[ruleName]
541
+ };
542
+ // eslint-disable-next-line jsdoc/require-jsdoc
543
+ function throwError(property) {
544
+ throw new Error(
545
+ `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
599
546
  }
600
- const fixInfo = errorInfo.fixInfo;
601
- const cleanFixInfo = {};
602
- if (fixInfo) {
603
- if (!helpers.isObject(fixInfo)) {
604
- throwError("fixInfo");
547
+ // eslint-disable-next-line jsdoc/require-jsdoc
548
+ function onError(errorInfo) {
549
+ if (!errorInfo ||
550
+ !helpers.isNumber(errorInfo.lineNumber) ||
551
+ (errorInfo.lineNumber < 1) ||
552
+ (errorInfo.lineNumber > lines.length)) {
553
+ throwError("lineNumber");
605
554
  }
606
- if (fixInfo.lineNumber !== undefined) {
607
- if ((!helpers.isNumber(fixInfo.lineNumber) ||
608
- (fixInfo.lineNumber < 1) ||
609
- (fixInfo.lineNumber > lines.length))) {
610
- throwError("fixInfo.lineNumber");
611
- }
612
- cleanFixInfo.lineNumber =
613
- fixInfo.lineNumber + frontMatterLines.length;
555
+ const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
556
+ if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
557
+ return;
614
558
  }
615
- const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
616
- if (fixInfo.editColumn !== undefined) {
617
- if ((!helpers.isNumber(fixInfo.editColumn) ||
618
- (fixInfo.editColumn < 1) ||
619
- (fixInfo.editColumn >
620
- lines[effectiveLineNumber - 1].length + 1))) {
621
- throwError("fixInfo.editColumn");
622
- }
623
- cleanFixInfo.editColumn = fixInfo.editColumn;
559
+ if (errorInfo.detail &&
560
+ !helpers.isString(errorInfo.detail)) {
561
+ throwError("detail");
624
562
  }
625
- if (fixInfo.deleteCount !== undefined) {
626
- if ((!helpers.isNumber(fixInfo.deleteCount) ||
627
- (fixInfo.deleteCount < -1) ||
628
- (fixInfo.deleteCount >
629
- lines[effectiveLineNumber - 1].length))) {
630
- throwError("fixInfo.deleteCount");
631
- }
632
- cleanFixInfo.deleteCount = fixInfo.deleteCount;
563
+ if (errorInfo.context &&
564
+ !helpers.isString(errorInfo.context)) {
565
+ throwError("context");
566
+ }
567
+ if (errorInfo.information &&
568
+ !helpers.isUrl(errorInfo.information)) {
569
+ throwError("information");
570
+ }
571
+ if (errorInfo.range &&
572
+ (!Array.isArray(errorInfo.range) ||
573
+ (errorInfo.range.length !== 2) ||
574
+ !helpers.isNumber(errorInfo.range[0]) ||
575
+ (errorInfo.range[0] < 1) ||
576
+ !helpers.isNumber(errorInfo.range[1]) ||
577
+ (errorInfo.range[1] < 1) ||
578
+ ((errorInfo.range[0] + errorInfo.range[1] - 1) >
579
+ lines[errorInfo.lineNumber - 1].length))) {
580
+ throwError("range");
633
581
  }
634
- if (fixInfo.insertText !== undefined) {
635
- if (!helpers.isString(fixInfo.insertText)) {
636
- throwError("fixInfo.insertText");
582
+ const fixInfo = errorInfo.fixInfo;
583
+ const cleanFixInfo = {};
584
+ if (fixInfo) {
585
+ if (!helpers.isObject(fixInfo)) {
586
+ throwError("fixInfo");
587
+ }
588
+ if (fixInfo.lineNumber !== undefined) {
589
+ if ((!helpers.isNumber(fixInfo.lineNumber) ||
590
+ (fixInfo.lineNumber < 1) ||
591
+ (fixInfo.lineNumber > lines.length))) {
592
+ throwError("fixInfo.lineNumber");
593
+ }
594
+ cleanFixInfo.lineNumber =
595
+ fixInfo.lineNumber + frontMatterLines.length;
596
+ }
597
+ const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
598
+ if (fixInfo.editColumn !== undefined) {
599
+ if ((!helpers.isNumber(fixInfo.editColumn) ||
600
+ (fixInfo.editColumn < 1) ||
601
+ (fixInfo.editColumn >
602
+ lines[effectiveLineNumber - 1].length + 1))) {
603
+ throwError("fixInfo.editColumn");
604
+ }
605
+ cleanFixInfo.editColumn = fixInfo.editColumn;
606
+ }
607
+ if (fixInfo.deleteCount !== undefined) {
608
+ if ((!helpers.isNumber(fixInfo.deleteCount) ||
609
+ (fixInfo.deleteCount < -1) ||
610
+ (fixInfo.deleteCount >
611
+ lines[effectiveLineNumber - 1].length))) {
612
+ throwError("fixInfo.deleteCount");
613
+ }
614
+ cleanFixInfo.deleteCount = fixInfo.deleteCount;
615
+ }
616
+ if (fixInfo.insertText !== undefined) {
617
+ if (!helpers.isString(fixInfo.insertText)) {
618
+ throwError("fixInfo.insertText");
619
+ }
620
+ cleanFixInfo.insertText = fixInfo.insertText;
637
621
  }
638
- cleanFixInfo.insertText = fixInfo.insertText;
639
622
  }
623
+ const information = errorInfo.information || rule.information;
624
+ results.push({
625
+ lineNumber,
626
+ "ruleName": rule.names[0],
627
+ "ruleNames": rule.names,
628
+ "ruleDescription": rule.description,
629
+ "ruleInformation": information ? information.href : null,
630
+ "errorDetail": errorInfo.detail || null,
631
+ "errorContext": errorInfo.context || null,
632
+ "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
633
+ "fixInfo": fixInfo ? cleanFixInfo : null
634
+ });
640
635
  }
641
- const information = errorInfo.information || rule.information;
642
- results.push({
643
- lineNumber,
644
- "ruleName": rule.names[0],
645
- "ruleNames": rule.names,
646
- "ruleDescription": rule.description,
647
- "ruleInformation": information ? information.href : null,
648
- "errorDetail": errorInfo.detail || null,
649
- "errorContext": errorInfo.context || null,
650
- "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
651
- "fixInfo": fixInfo ? cleanFixInfo : null
636
+ // Call (possibly external) rule function to report errors
637
+ const catchCallsOnError = (error) => onError({
638
+ "lineNumber": 1,
639
+ "detail": `This rule threw an exception: ${error.message || error}`
652
640
  });
653
- }
654
- // Call (possibly external) rule function to report errors
655
- const catchCallsOnError = (error) => onError({
656
- "lineNumber": 1,
657
- "detail": `This rule threw an exception: ${error.message || error}`
658
- });
659
- const invokeRuleFunction = () => rule.function(params, onError);
660
- if (rule.asynchronous) {
661
- // Asynchronous rule, ensure it returns a Promise
662
- const ruleFunctionPromise =
663
- Promise.resolve().then(invokeRuleFunction);
664
- return handleRuleFailures ?
665
- ruleFunctionPromise.catch(catchCallsOnError) :
666
- ruleFunctionPromise;
667
- }
668
- // Synchronous rule
669
- try {
670
- invokeRuleFunction();
671
- } catch (error) {
672
- if (handleRuleFailures) {
673
- catchCallsOnError(error);
674
- } else {
675
- throw error;
641
+ const invokeRuleFunction = () => rule.function(params, onError);
642
+ if (rule.asynchronous) {
643
+ // Asynchronous rule, ensure it returns a Promise
644
+ const ruleFunctionPromise =
645
+ Promise.resolve().then(invokeRuleFunction);
646
+ return handleRuleFailures ?
647
+ ruleFunctionPromise.catch(catchCallsOnError) :
648
+ ruleFunctionPromise;
676
649
  }
677
- }
678
- return null;
679
- };
680
- // eslint-disable-next-line jsdoc/require-jsdoc
681
- function formatResults() {
682
- // Sort results by rule name by line number
683
- results.sort((a, b) => (
684
- a.ruleName.localeCompare(b.ruleName) ||
685
- a.lineNumber - b.lineNumber
686
- ));
687
- if (resultVersion < 3) {
688
- // Remove fixInfo and multiple errors for the same rule and line number
689
- const noPrevious = {
690
- "ruleName": null,
691
- "lineNumber": -1
692
- };
693
- results = results.filter((error, index, array) => {
694
- delete error.fixInfo;
695
- const previous = array[index - 1] || noPrevious;
696
- return (
697
- (error.ruleName !== previous.ruleName) ||
698
- (error.lineNumber !== previous.lineNumber)
699
- );
700
- });
701
- }
702
- if (resultVersion === 0) {
703
- // Return a dictionary of rule->[line numbers]
704
- const dictionary = {};
705
- for (const error of results) {
706
- const ruleLines = dictionary[error.ruleName] || [];
707
- ruleLines.push(error.lineNumber);
708
- dictionary[error.ruleName] = ruleLines;
650
+ // Synchronous rule
651
+ try {
652
+ invokeRuleFunction();
653
+ } catch (error) {
654
+ if (handleRuleFailures) {
655
+ catchCallsOnError(error);
656
+ } else {
657
+ throw error;
658
+ }
709
659
  }
710
- // @ts-ignore
711
- results = dictionary;
712
- } else if (resultVersion === 1) {
713
- // Use ruleAlias instead of ruleNames
714
- for (const error of results) {
715
- error.ruleAlias = error.ruleNames[1] || error.ruleName;
716
- delete error.ruleNames;
660
+ return null;
661
+ };
662
+ const formatResults = () => {
663
+ // Sort results by rule name by line number
664
+ results.sort((a, b) => (
665
+ a.ruleName.localeCompare(b.ruleName) ||
666
+ a.lineNumber - b.lineNumber
667
+ ));
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
+ });
717
682
  }
718
- } else {
719
- // resultVersion 2 or 3: Remove unwanted ruleName
720
- for (const error of results) {
721
- delete error.ruleName;
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
+ }
722
704
  }
705
+ return results;
706
+ };
707
+ // Run all rules
708
+ const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
709
+ const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
710
+ const ruleListAsyncFirst = [
711
+ ...ruleListAsync,
712
+ ...ruleListSync
713
+ ];
714
+ const callbackSuccess = () => callback(null, formatResults());
715
+ try {
716
+ const ruleResults = ruleListAsyncFirst.map(forRule);
717
+ if (ruleListAsync.length > 0) {
718
+ Promise.all(ruleResults.slice(0, ruleListAsync.length))
719
+ .then(callbackSuccess)
720
+ .catch(callbackError);
721
+ } else {
722
+ callbackSuccess();
723
+ }
724
+ } catch (error) {
725
+ callbackError(error);
726
+ } finally {
727
+ cacheInitialize();
723
728
  }
724
- return results;
725
- }
726
- // Run all rules
727
- const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
728
- const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
729
- const ruleListAsyncFirst = [
730
- ...ruleListAsync,
731
- ...ruleListSync
732
- ];
733
- const callbackSuccess = () => callback(null, formatResults());
734
- const callbackError =
735
- (error) => callback(error instanceof Error ? error : new Error(error));
736
- try {
737
- const ruleResults = ruleListAsyncFirst.map(forRule);
738
- if (ruleListAsync.length > 0) {
739
- Promise.all(ruleResults.slice(0, ruleListAsync.length))
740
- .then(callbackSuccess)
741
- .catch(callbackError);
742
- } else {
743
- callbackSuccess();
744
- }
745
- } catch (error) {
746
- callbackError(error);
747
- } finally {
748
- cacheInitialize();
729
+ };
730
+ if (!needMarkdownItTokens || synchronous) {
731
+ // Need/able to call into markdown-it and lintContentInternal synchronously
732
+ const markdownItTokens = needMarkdownItTokens ?
733
+ requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), preClearedContent, lines) :
734
+ [];
735
+ lintContentInternal(markdownItTokens);
736
+ } else {
737
+ // Need to call into markdown-it and lintContentInternal asynchronously
738
+ Promise.all([
739
+ // eslint-disable-next-line no-inline-comments
740
+ import(/* webpackMode: "eager" */ "./markdownit.cjs"),
741
+ // eslint-disable-next-line no-promise-executor-return
742
+ new Promise((resolve) => resolve(markdownItFactory()))
743
+ ]).then(([ markdownitCjs, markdownIt ]) => {
744
+ const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, preClearedContent, lines);
745
+ lintContentInternal(markdownItTokens);
746
+ }).catch(callbackError);
749
747
  }
750
748
  }
751
749
 
@@ -753,12 +751,11 @@ function lintContent(
753
751
  * Lints a file containing Markdown content.
754
752
  *
755
753
  * @param {Rule[]} ruleList List of rules.
756
- * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
757
- * names.
754
+ * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
758
755
  * @param {string} file Path of file to lint.
759
- * @param {Plugin[]} markdownItPlugins Additional plugins.
756
+ * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
760
757
  * @param {Configuration} config Configuration object.
761
- * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
758
+ * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
762
759
  * @param {RegExp | null} frontMatter Regular expression for front matter.
763
760
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
764
761
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
@@ -772,7 +769,7 @@ function lintFile(
772
769
  ruleList,
773
770
  aliasToRuleNames,
774
771
  file,
775
- markdownItPlugins,
772
+ markdownItFactory,
776
773
  config,
777
774
  configParsers,
778
775
  frontMatter,
@@ -792,13 +789,14 @@ function lintFile(
792
789
  aliasToRuleNames,
793
790
  file,
794
791
  content,
795
- markdownItPlugins,
792
+ markdownItFactory,
796
793
  config,
797
794
  configParsers,
798
795
  frontMatter,
799
796
  handleRuleFailures,
800
797
  noInlineConfig,
801
798
  resultVersion,
799
+ synchronous,
802
800
  callback
803
801
  );
804
802
  }
@@ -850,7 +848,7 @@ function lintInput(options, synchronous, callback) {
850
848
  const strings = options.strings || {};
851
849
  const stringsKeys = Object.keys(strings);
852
850
  const config = options.config || { "default": true };
853
- const configParsers = options.configParsers || null;
851
+ const configParsers = options.configParsers || undefined;
854
852
  const frontMatter = (options.frontMatter === undefined) ?
855
853
  helpers.frontMatterRe :
856
854
  options.frontMatter;
@@ -859,7 +857,9 @@ function lintInput(options, synchronous, callback) {
859
857
  const resultVersion = (options.resultVersion === undefined) ?
860
858
  3 :
861
859
  options.resultVersion;
862
- const markdownItPlugins = options.markdownItPlugins || [];
860
+ const markdownItFactory =
861
+ options.markdownItFactory ||
862
+ (() => { 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."); });
863
863
  const fs = options.fs || nodeFs;
864
864
  const aliasToRuleNames = mapAliasToRuleNames(ruleList);
865
865
  const results = newResults(ruleList);
@@ -891,7 +891,7 @@ function lintInput(options, synchronous, callback) {
891
891
  ruleList,
892
892
  aliasToRuleNames,
893
893
  currentItem,
894
- markdownItPlugins,
894
+ markdownItFactory,
895
895
  config,
896
896
  configParsers,
897
897
  frontMatter,
@@ -910,13 +910,14 @@ function lintInput(options, synchronous, callback) {
910
910
  aliasToRuleNames,
911
911
  currentItem,
912
912
  strings[currentItem] || "",
913
- markdownItPlugins,
913
+ markdownItFactory,
914
914
  config,
915
915
  configParsers,
916
916
  frontMatter,
917
917
  handleRuleFailures,
918
918
  noInlineConfig,
919
919
  resultVersion,
920
+ synchronous,
920
921
  lintWorkerCallback
921
922
  );
922
923
  } else if (concurrency === 0) {
@@ -1010,10 +1011,10 @@ function resolveConfigExtends(configFile, referenceId, fs, callback) {
1010
1011
  if (err) {
1011
1012
  // Not a file, try require.resolve
1012
1013
  try {
1013
- return callback(null, dynamicRequire.resolve(
1014
- referenceId,
1015
- { "paths": [ configFileDirname ] }
1016
- ));
1014
+ return callback(
1015
+ null,
1016
+ resolveModule(referenceId, [ configFileDirname ])
1017
+ );
1017
1018
  } catch {
1018
1019
  // Unable to resolve, use resolvedExtendsFile
1019
1020
  }
@@ -1041,10 +1042,7 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) {
1041
1042
  // Not a file, try require.resolve
1042
1043
  }
1043
1044
  try {
1044
- return dynamicRequire.resolve(
1045
- referenceId,
1046
- { "paths": [ configFileDirname ] }
1047
- );
1045
+ return resolveModule(referenceId, [ configFileDirname ]);
1048
1046
  } catch {
1049
1047
  // Unable to resolve, return resolvedExtendsFile
1050
1048
  }
@@ -1196,6 +1194,7 @@ export function readConfigSync(file, parsers, fs) {
1196
1194
  // Try to parse file
1197
1195
  const { config, message } = parseConfiguration(file, content, parsers);
1198
1196
  if (!config) {
1197
+ // @ts-ignore
1199
1198
  throw new Error(message);
1200
1199
  }
1201
1200
  // Extend configuration
@@ -1475,6 +1474,29 @@ export function getVersion() {
1475
1474
  * @property {RuleFunction} function Rule implementation.
1476
1475
  */
1477
1476
 
1477
+ /**
1478
+ * Method used by the markdown-it parser to parse input.
1479
+ *
1480
+ * @callback MarkdownItParse
1481
+ * @param {string} src Source string.
1482
+ * @param {Object} env Environment sandbox.
1483
+ * @returns {import("markdown-it").Token[]} Tokens.
1484
+ */
1485
+
1486
+ /**
1487
+ * Instance of the markdown-it parser.
1488
+ *
1489
+ * @typedef MarkdownIt
1490
+ * @property {MarkdownItParse} parse Method to parse input.
1491
+ */
1492
+
1493
+ /**
1494
+ * Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
1495
+ *
1496
+ * @callback MarkdownItFactory
1497
+ * @returns {MarkdownIt|Promise<MarkdownIt>} Instance of the markdown-it parser.
1498
+ */
1499
+
1478
1500
  /**
1479
1501
  * Configuration options.
1480
1502
  *
@@ -1486,7 +1508,7 @@ export function getVersion() {
1486
1508
  * @property {RegExp | null} [frontMatter] Front matter pattern.
1487
1509
  * @property {Object} [fs] File system implementation.
1488
1510
  * @property {boolean} [handleRuleFailures] True to catch exceptions.
1489
- * @property {Plugin[]} [markdownItPlugins] Additional plugins.
1511
+ * @property {MarkdownItFactory} [markdownItFactory] Function to create a markdown-it parser.
1490
1512
  * @property {boolean} [noInlineConfig] True to ignore HTML directives.
1491
1513
  * @property {number} [resultVersion] Results object version.
1492
1514
  * @property {Object.<string, string>} [strings] Strings to lint.
@@ -1503,7 +1525,7 @@ export function getVersion() {
1503
1525
  *
1504
1526
  * @callback ToStringCallback
1505
1527
  * @param {boolean} [ruleAliases] True to use rule aliases.
1506
- * @returns {string}
1528
+ * @returns {string} Pretty-printed results.
1507
1529
  */
1508
1530
 
1509
1531
  /**