markdownlint 0.41.0 → 0.42.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 (66) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/CONTRIBUTING.md +62 -77
  3. package/README.md +7 -7
  4. package/doc/CustomRules.md +6 -4
  5. package/doc/Rules.md +87 -36
  6. package/doc/md010.md +0 -1
  7. package/doc/md024.md +1 -1
  8. package/doc/md029.md +11 -1
  9. package/doc/md043.md +47 -9
  10. package/doc/md051.md +19 -19
  11. package/doc/md055.md +6 -3
  12. package/doc/md060.md +3 -2
  13. package/helpers/README.md +2 -2
  14. package/helpers/helpers.cjs +23 -7
  15. package/helpers/micromark-helpers.cjs +8 -12
  16. package/helpers/package.json +1 -5
  17. package/helpers/shared.cjs +1 -1
  18. package/lib/cache.mjs +1 -0
  19. package/lib/configuration-strict.d.ts +124 -112
  20. package/lib/constants.mjs +1 -1
  21. package/lib/defer-require.cjs +1 -0
  22. package/lib/exports.d.mts +29 -1
  23. package/lib/markdownit.cjs +8 -1
  24. package/lib/markdownlint.d.mts +337 -195
  25. package/lib/markdownlint.mjs +83 -87
  26. package/lib/md004.mjs +1 -0
  27. package/lib/md005.mjs +1 -1
  28. package/lib/md007.mjs +2 -2
  29. package/lib/md009.mjs +2 -3
  30. package/lib/md010.mjs +1 -1
  31. package/lib/md011.mjs +10 -7
  32. package/lib/md012.mjs +1 -1
  33. package/lib/md013.mjs +13 -9
  34. package/lib/md018.mjs +1 -1
  35. package/lib/md019-md021.mjs +1 -0
  36. package/lib/md020.mjs +1 -1
  37. package/lib/md022.mjs +6 -6
  38. package/lib/md024.mjs +1 -1
  39. package/lib/md025.mjs +1 -1
  40. package/lib/md027.mjs +1 -1
  41. package/lib/md028.mjs +1 -2
  42. package/lib/md029.mjs +34 -23
  43. package/lib/md030.mjs +4 -4
  44. package/lib/md031.mjs +1 -1
  45. package/lib/md032.mjs +5 -4
  46. package/lib/md033.mjs +1 -1
  47. package/lib/md034.mjs +1 -1
  48. package/lib/md037.mjs +1 -2
  49. package/lib/md039.mjs +5 -1
  50. package/lib/md041.mjs +1 -1
  51. package/lib/md043.mjs +98 -44
  52. package/lib/md044.mjs +2 -2
  53. package/lib/md048.mjs +1 -3
  54. package/lib/md051.mjs +3 -3
  55. package/lib/md055.mjs +11 -1
  56. package/lib/md056.mjs +1 -1
  57. package/lib/md058.mjs +1 -1
  58. package/lib/md060.mjs +24 -4
  59. package/lib/micromark-parse.mjs +3 -2
  60. package/lib/resolve-module.cjs +3 -2
  61. package/lib/resolve-module.d.cts +15 -10
  62. package/package.json +33 -34
  63. package/schema/.markdownlint.jsonc +54 -54
  64. package/schema/.markdownlint.yaml +76 -76
  65. package/schema/markdownlint-config-schema-strict.json +126 -118
  66. package/schema/markdownlint-config-schema.json +126 -118
@@ -2,6 +2,7 @@
2
2
 
3
3
  // @ts-ignore
4
4
  import { fs as nodeFs, os, path } from "#node-imports";
5
+ import { RE2JS } from "re2js";
5
6
  import { initialize as cacheInitialize } from "./cache.mjs";
6
7
  import { version } from "./constants.mjs";
7
8
  import { requireMarkdownItCjs } from "./defer-require.cjs";
@@ -76,7 +77,7 @@ function validateRuleList(ruleList, synchronous) {
76
77
  ) {
77
78
  result = newError("asynchronous", rule.asynchronous);
78
79
  }
79
- if (!result && rule.asynchronous && synchronous) {
80
+ if (!result && synchronous && rule.asynchronous) {
80
81
  result = new Error(
81
82
  "Custom rule " + rule.names.join("/") + " at index " + customIndex +
82
83
  " is asynchronous and can not be used in a synchronous context."
@@ -93,6 +94,7 @@ function validateRuleList(ruleList, synchronous) {
93
94
  }
94
95
  for (const tag of rule.tags) {
95
96
  const tagUpper = tag.toUpperCase();
97
+ // eslint-disable-next-line unicorn/no-computed-property-existence-check
96
98
  if (!result && allIds[tagUpper]) {
97
99
  result = new Error("Tag '" + tag + "' of custom rule at index " +
98
100
  customIndex + " is already used as a name.");
@@ -116,14 +118,15 @@ function validateRuleList(ruleList, synchronous) {
116
118
  * Remove front matter (if present at beginning of content).
117
119
  *
118
120
  * @param {string} content Markdown content.
119
- * @param {RegExp | null} frontMatter Regular expression to match front matter.
121
+ * @param {RE2JS | null} frontMatterRe2 Regular expression to match front matter.
120
122
  * @returns {RemoveFrontMatterResult} Trimmed content and front matter lines.
121
123
  */
122
- function removeFrontMatter(content, frontMatter) {
124
+ function removeFrontMatter(content, frontMatterRe2) {
123
125
  /** @type {string[]} */
124
126
  let frontMatterLines = [];
125
- if (frontMatter) {
126
- const frontMatterMatch = content.match(frontMatter);
127
+ if (frontMatterRe2) {
128
+ const frontMatterMatch = frontMatterRe2.exec(content);
129
+ // @ts-ignore
127
130
  if (frontMatterMatch && !frontMatterMatch.index) {
128
131
  const contentMatched = frontMatterMatch[0];
129
132
  content = content.slice(contentMatched.length);
@@ -198,7 +201,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
198
201
  let ruleDefaultEnable = true;
199
202
  /** @type {"error" | "warning"} */
200
203
  let ruleDefaultSeverity = "error";
201
- // eslint-disable-next-line unicorn/no-unused-array-method-return
204
+ // eslint-disable-next-line unicorn/no-unused-builtin-method-return
202
205
  Object.entries(config).every(([ key, value ]) => {
203
206
  if (key.toUpperCase() === "DEFAULT") {
204
207
  ruleDefaultEnable = !!value;
@@ -235,7 +238,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
235
238
  if (value instanceof Object) {
236
239
  /** @type {{ enabled?: boolean, severity?: "error" | "warning" }} */
237
240
  const valueObject = value;
238
- enabled = (valueObject.enabled === undefined) ? true : !!valueObject.enabled;
241
+ enabled = (valueObject.enabled === undefined) || !!valueObject.enabled;
239
242
  severity = (valueObject.severity === "warning") ? "warning" : "error";
240
243
  effectiveValue = Object.fromEntries(
241
244
  Object.entries(value).filter(
@@ -276,7 +279,6 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
276
279
  *
277
280
  * @param {Rule[]} ruleList List of rules.
278
281
  * @param {string[]} lines List of content lines.
279
- * @param {string[]} frontMatterLines List of front matter lines.
280
282
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
281
283
  * @param {Configuration} config Configuration object.
282
284
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
@@ -286,7 +288,6 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
286
288
  function getEnabledRulesPerLineNumber(
287
289
  ruleList,
288
290
  lines,
289
- frontMatterLines,
290
291
  noInlineConfig,
291
292
  config,
292
293
  configParsers,
@@ -296,7 +297,7 @@ function getEnabledRulesPerLineNumber(
296
297
  let enabledRules = new Map();
297
298
  /** @type {Map<string, boolean>} */
298
299
  let capturedRules = enabledRules;
299
- const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
300
+ const enabledRulesPerLineNumber = new Array(1);
300
301
  // Helper functions
301
302
  // eslint-disable-next-line jsdoc/require-jsdoc
302
303
  function handleInlineConfig(/** @type {string[]} */ input, /** @type {(act: string, par: string, ind: number) => void} */ forEachMatch, /** @type {(() => void)|undefined} */ forEachLine = undefined) {
@@ -372,8 +373,7 @@ function getEnabledRulesPerLineNumber(
372
373
  const disableLine = (action === "DISABLE-LINE");
373
374
  const disableNextLine = (action === "DISABLE-NEXT-LINE");
374
375
  if (disableLine || disableNextLine) {
375
- const nextLineNumber =
376
- frontMatterLines.length + lineNumber + (disableNextLine ? 1 : 0);
376
+ const nextLineNumber = lineNumber + (disableNextLine ? 1 : 0);
377
377
  enabledRulesPerLineNumber[nextLineNumber] =
378
378
  applyEnableDisable(
379
379
  action,
@@ -415,7 +415,7 @@ function getEnabledRulesPerLineNumber(
415
415
  * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
416
416
  * @param {Configuration} config Configuration object.
417
417
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
418
- * @param {RegExp | null} frontMatter Regular expression for front matter.
418
+ * @param {RE2JS | null} frontMatterRe2 Regular expression for front matter.
419
419
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
420
420
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
421
421
  * @param {boolean} synchronous Whether to execute synchronously.
@@ -430,7 +430,7 @@ function lintContent(
430
430
  markdownItFactory,
431
431
  config,
432
432
  configParsers,
433
- frontMatter,
433
+ frontMatterRe2,
434
434
  handleRuleFailures,
435
435
  noInlineConfig,
436
436
  synchronous,
@@ -439,17 +439,17 @@ function lintContent(
439
439
  // eslint-disable-next-line jsdoc/reject-any-type
440
440
  const callbackError = (/** @type {any} */ error) => callback(error instanceof Error ? error : new Error(error));
441
441
  // Remove UTF-8 byte order marker (if present)
442
- content = content.replace(/^\uFEFF/, "");
442
+ // eslint-disable-next-line unicorn/prefer-unicode-code-point-escapes
443
+ const fullContent = content.replace(/^\uFEFF/, "");
444
+ const fullContentLines = fullContent.split(helpers.newLineRe);
445
+ const fullContentNormalized = fullContentLines.join("\n");
443
446
  // Remove front matter
444
- const removeFrontMatterResult = removeFrontMatter(content, frontMatter);
445
- const { frontMatterLines } = removeFrontMatterResult;
446
- content = removeFrontMatterResult.content;
447
+ const { "content": markdownContentNormalized, frontMatterLines } = removeFrontMatter(fullContentNormalized, frontMatterRe2);
447
448
  // Get enabled rules per line (with HTML comments present)
448
449
  const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList, rulesSeverity } =
449
450
  getEnabledRulesPerLineNumber(
450
451
  ruleList,
451
- content.split(helpers.newLineRe),
452
- frontMatterLines,
452
+ fullContentLines,
453
453
  noInlineConfig,
454
454
  config,
455
455
  configParsers,
@@ -464,13 +464,12 @@ function lintContent(
464
464
  const customRulesPresent = (ruleList.length !== rules.length);
465
465
  // Parse content into parser tokens
466
466
  const micromarkTokens = needMicromarkTokens ?
467
- micromarkParse(content, { "freezeTokens": customRulesPresent }) :
467
+ micromarkParse(markdownContentNormalized, { "freezeTokens": customRulesPresent }) :
468
468
  [];
469
469
  // Hide the content of HTML comments from rules
470
- const preClearedContent = content;
471
- content = helpers.clearHtmlCommentText(content);
470
+ const markdownContentNormalizedSanitized = helpers.clearHtmlCommentText(markdownContentNormalized);
472
471
  // Parse content into lines and get markdown-it tokens
473
- const lines = content.split(helpers.newLineRe);
472
+ const lines = markdownContentNormalizedSanitized.split(helpers.newLineRe);
474
473
  // Function to run after fetching markdown-it tokens (when needed)
475
474
  const lintContentInternal = (/** @type {MarkdownItToken[]} */ markdownitTokens) => {
476
475
  // Create (frozen) parameters for rules
@@ -537,37 +536,45 @@ function lintContent(
537
536
  }
538
537
  // eslint-disable-next-line jsdoc/require-jsdoc
539
538
  function onError(/** @type {RuleOnErrorInfo} */ errorInfo) {
540
- if (!errorInfo ||
541
- !helpers.isNumber(errorInfo.lineNumber) ||
539
+ if (!errorInfo) {
540
+ throwError("onErrorInfo");
541
+ }
542
+ if ((errorInfo.frontMatter !== undefined) &&
543
+ !helpers.isBoolean(errorInfo.frontMatter)) {
544
+ throwError("frontMatter");
545
+ }
546
+ const useFrontMatter = Boolean(errorInfo.frontMatter);
547
+ const contentLines = useFrontMatter ? frontMatterLines : lines;
548
+ const lineOffset = useFrontMatter ? 0 : frontMatterLines.length;
549
+ if (!helpers.isNumber(errorInfo.lineNumber) ||
542
550
  (errorInfo.lineNumber < 1) ||
543
- (errorInfo.lineNumber > lines.length)) {
551
+ (errorInfo.lineNumber > contentLines.length)) {
544
552
  throwError("lineNumber");
545
553
  }
546
- const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
554
+ const lineNumber = errorInfo.lineNumber + lineOffset;
547
555
  if (!enabledRulesPerLineNumber[lineNumber].get(ruleName)) {
548
556
  return;
549
557
  }
550
- if (errorInfo.detail &&
558
+ if ((errorInfo.detail !== undefined) &&
551
559
  !helpers.isString(errorInfo.detail)) {
552
560
  throwError("detail");
553
561
  }
554
- if (errorInfo.context &&
562
+ if ((errorInfo.context !== undefined) &&
555
563
  !helpers.isString(errorInfo.context)) {
556
564
  throwError("context");
557
565
  }
558
- if (errorInfo.information &&
566
+ if ((errorInfo.information !== undefined) &&
559
567
  !helpers.isUrl(errorInfo.information)) {
560
568
  throwError("information");
561
569
  }
562
- if (errorInfo.range &&
570
+ if ((errorInfo.range !== undefined) &&
563
571
  (!Array.isArray(errorInfo.range) ||
564
572
  (errorInfo.range.length !== 2) ||
565
573
  !helpers.isNumber(errorInfo.range[0]) ||
566
574
  (errorInfo.range[0] < 1) ||
567
575
  !helpers.isNumber(errorInfo.range[1]) ||
568
576
  (errorInfo.range[1] < 1) ||
569
- ((errorInfo.range[0] + errorInfo.range[1] - 1) >
570
- lines[errorInfo.lineNumber - 1].length))) {
577
+ ((errorInfo.range[0] + errorInfo.range[1] - 1) > contentLines[errorInfo.lineNumber - 1].length))) {
571
578
  throwError("range");
572
579
  }
573
580
  const fixInfo = errorInfo.fixInfo;
@@ -579,18 +586,16 @@ function lintContent(
579
586
  if (fixInfo.lineNumber !== undefined) {
580
587
  if ((!helpers.isNumber(fixInfo.lineNumber) ||
581
588
  (fixInfo.lineNumber < 1) ||
582
- (fixInfo.lineNumber > lines.length))) {
589
+ (fixInfo.lineNumber > contentLines.length))) {
583
590
  throwError("fixInfo.lineNumber");
584
591
  }
585
- cleanFixInfo.lineNumber =
586
- fixInfo.lineNumber + frontMatterLines.length;
592
+ cleanFixInfo.lineNumber = fixInfo.lineNumber + lineOffset;
587
593
  }
588
594
  const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
589
595
  if (fixInfo.editColumn !== undefined) {
590
596
  if ((!helpers.isNumber(fixInfo.editColumn) ||
591
597
  (fixInfo.editColumn < 1) ||
592
- (fixInfo.editColumn >
593
- lines[effectiveLineNumber - 1].length + 1))) {
598
+ (fixInfo.editColumn > contentLines[effectiveLineNumber - 1].length + 1))) {
594
599
  throwError("fixInfo.editColumn");
595
600
  }
596
601
  cleanFixInfo.editColumn = fixInfo.editColumn;
@@ -598,8 +603,7 @@ function lintContent(
598
603
  if (fixInfo.deleteCount !== undefined) {
599
604
  if ((!helpers.isNumber(fixInfo.deleteCount) ||
600
605
  (fixInfo.deleteCount < -1) ||
601
- (fixInfo.deleteCount >
602
- lines[effectiveLineNumber - 1].length))) {
606
+ (((fixInfo.editColumn ?? 1) + fixInfo.deleteCount - 1) > contentLines[effectiveLineNumber - 1].length))) {
603
607
  throwError("fixInfo.deleteCount");
604
608
  }
605
609
  cleanFixInfo.deleteCount = fixInfo.deleteCount;
@@ -686,7 +690,7 @@ function lintContent(
686
690
  if (!needMarkdownItTokens || synchronous) {
687
691
  // Need/able to call into markdown-it and lintContentInternal synchronously
688
692
  const markdownItTokens = needMarkdownItTokens ?
689
- requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), preClearedContent, lines) :
693
+ requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), markdownContentNormalized, lines) :
690
694
  [];
691
695
  lintContentInternal(markdownItTokens);
692
696
  } else {
@@ -697,7 +701,7 @@ function lintContent(
697
701
  // eslint-disable-next-line no-promise-executor-return
698
702
  new Promise((resolve) => resolve(markdownItFactory()))
699
703
  ]).then(([ markdownitCjs, markdownIt ]) => {
700
- const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, preClearedContent, lines);
704
+ const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, markdownContentNormalized, lines);
701
705
  lintContentInternal(markdownItTokens);
702
706
  }).catch(callbackError);
703
707
  }
@@ -712,7 +716,7 @@ function lintContent(
712
716
  * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
713
717
  * @param {Configuration} config Configuration object.
714
718
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
715
- * @param {RegExp | null} frontMatter Regular expression for front matter.
719
+ * @param {RE2JS | null} frontMatterRe2 Regular expression for front matter.
716
720
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
717
721
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
718
722
  * @param {FsLike} fs File system implementation.
@@ -727,7 +731,7 @@ function lintFile(
727
731
  markdownItFactory,
728
732
  config,
729
733
  configParsers,
730
- frontMatter,
734
+ frontMatterRe2,
731
735
  handleRuleFailures,
732
736
  noInlineConfig,
733
737
  fs,
@@ -735,23 +739,22 @@ function lintFile(
735
739
  callback) {
736
740
  // eslint-disable-next-line jsdoc/require-jsdoc
737
741
  function lintContentWrapper(/** @type {Error | null} */ err, /** @type {string} */ content) {
738
- if (err) {
739
- return callback(err);
740
- }
741
- return lintContent(
742
- ruleList,
743
- aliasToRuleNames,
744
- file,
745
- content,
746
- markdownItFactory,
747
- config,
748
- configParsers,
749
- frontMatter,
750
- handleRuleFailures,
751
- noInlineConfig,
752
- synchronous,
753
- callback
754
- );
742
+ return err ?
743
+ callback(err) :
744
+ lintContent(
745
+ ruleList,
746
+ aliasToRuleNames,
747
+ file,
748
+ content,
749
+ markdownItFactory,
750
+ config,
751
+ configParsers,
752
+ frontMatterRe2,
753
+ handleRuleFailures,
754
+ noInlineConfig,
755
+ synchronous,
756
+ callback
757
+ );
755
758
  }
756
759
  // Make a/synchronous call to read file
757
760
  if (synchronous) {
@@ -771,8 +774,8 @@ function lintFile(
771
774
  */
772
775
  function lintInput(options, synchronous, callback) {
773
776
  // Normalize inputs
774
- options = options || {};
775
- callback = callback || function noop() {};
777
+ options ||= {};
778
+ callback ||= () => {};
776
779
  /** @type {Rule[]} */
777
780
  // @ts-ignore
778
781
  const customRuleList =
@@ -805,9 +808,8 @@ function lintInput(options, synchronous, callback) {
805
808
  const stringsKeys = Object.keys(strings);
806
809
  const config = options.config || { "default": true };
807
810
  const configParsers = options.configParsers || undefined;
808
- const frontMatter = (options.frontMatter === undefined) ?
809
- helpers.frontMatterRe :
810
- options.frontMatter;
811
+ const frontMatterRe = (options.frontMatter === undefined) ? helpers.frontMatterRe : options.frontMatter;
812
+ const frontMatterRe2 = frontMatterRe && RE2JS.compile(RE2JS.translateRegExp(frontMatterRe));
811
813
  const handleRuleFailures = !!options.handleRuleFailures;
812
814
  const noInlineConfig = !!options.noInlineConfig;
813
815
  const markdownItFactory =
@@ -850,7 +852,7 @@ function lintInput(options, synchronous, callback) {
850
852
  markdownItFactory,
851
853
  config,
852
854
  configParsers,
853
- frontMatter,
855
+ frontMatterRe2,
854
856
  handleRuleFailures,
855
857
  noInlineConfig,
856
858
  fs,
@@ -868,7 +870,7 @@ function lintInput(options, synchronous, callback) {
868
870
  markdownItFactory,
869
871
  config,
870
872
  configParsers,
871
- frontMatter,
873
+ frontMatterRe2,
872
874
  handleRuleFailures,
873
875
  noInlineConfig,
874
876
  synchronous,
@@ -1027,8 +1029,8 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) {
1027
1029
  */
1028
1030
  function extendConfig(config, file, parsers, fs, callback) {
1029
1031
  const configExtends = config.extends;
1030
- if (configExtends) {
1031
- return resolveConfigExtends(
1032
+ return configExtends ?
1033
+ resolveConfigExtends(
1032
1034
  file,
1033
1035
  helpers.expandTildePath(configExtends, os),
1034
1036
  fs,
@@ -1050,9 +1052,8 @@ function extendConfig(config, file, parsers, fs, callback) {
1050
1052
  return callback(null, result);
1051
1053
  }
1052
1054
  )
1053
- );
1054
- }
1055
- return callback(null, config);
1055
+ ) :
1056
+ callback(null, config);
1056
1057
  }
1057
1058
 
1058
1059
  /**
@@ -1112,13 +1113,11 @@ export function readConfigAsync(file, parsers, fs, callback) {
1112
1113
  // Try to parse file
1113
1114
  // @ts-ignore
1114
1115
  const { config, message } = parseConfiguration(file, content, parsers);
1115
- if (!config) {
1116
+ return config ?
1116
1117
  // @ts-ignore
1117
- return callback(new Error(message));
1118
- }
1119
- // Extend configuration
1120
- // @ts-ignore
1121
- return extendConfig(config, file, parsers, fsLike, callback);
1118
+ extendConfig(config, file, parsers, fsLike, callback) :
1119
+ // @ts-ignore
1120
+ callback(new Error(message));
1122
1121
  });
1123
1122
  }
1124
1123
 
@@ -1209,6 +1208,7 @@ export function applyFix(line, fixInfo, lineEnding = "\n") {
1209
1208
  const editIndex = editColumn - 1;
1210
1209
  return (deleteCount === -1) ?
1211
1210
  null :
1211
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement
1212
1212
  line.slice(0, editIndex) + insertText.replace(/\n/g, lineEnding) + line.slice(editIndex + deleteCount);
1213
1213
  }
1214
1214
 
@@ -1408,6 +1408,7 @@ export function getVersion() {
1408
1408
  * Fix information for RuleOnError callback.
1409
1409
  *
1410
1410
  * @typedef {Object} RuleOnErrorInfo
1411
+ * @property {boolean} [frontMatter] True if applies to front matter.
1411
1412
  * @property {number} lineNumber Line number (1-based).
1412
1413
  * @property {string} [detail] Detail about the error.
1413
1414
  * @property {string} [context] Context for the error.
@@ -1448,12 +1449,7 @@ export function getVersion() {
1448
1449
  * @returns {MarkdownItBaseToken[]} Tokens.
1449
1450
  */
1450
1451
 
1451
- /**
1452
- * Instance of the markdown-it parser.
1453
- *
1454
- * @typedef MarkdownIt
1455
- * @property {MarkdownItParse} parse Method to parse input.
1456
- */
1452
+ /** @typedef {import("markdown-it").MarkdownIt} MarkdownIt */
1457
1453
 
1458
1454
  /**
1459
1455
  * Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
@@ -1487,7 +1483,7 @@ export function getVersion() {
1487
1483
  /**
1488
1484
  * Lint results.
1489
1485
  *
1490
- * @typedef {Object.<string, LintError[]>} LintResults
1486
+ * @typedef {{[k: string]: LintError[]}} LintResults
1491
1487
  */
1492
1488
 
1493
1489
  /**
package/lib/md004.mjs CHANGED
@@ -40,6 +40,7 @@ export default {
40
40
  for (const listItemMarker of listItemMarkers) {
41
41
  const itemStyle = markerToStyle(listItemMarker.text);
42
42
  if (style === "sublist") {
43
+ // eslint-disable-next-line unicorn/no-computed-property-existence-check
43
44
  if (!nestingStyles[nesting]) {
44
45
  nestingStyles[nesting] =
45
46
  (itemStyle === nestingStyles[nesting - 1]) ?
package/lib/md005.mjs CHANGED
@@ -34,7 +34,7 @@ export default {
34
34
  } else {
35
35
  const markerLength = listItemPrefix.text.trim().length;
36
36
  const actualEnd = listItemPrefix.startColumn + markerLength - 1;
37
- expectedEnd = expectedEnd || actualEnd;
37
+ expectedEnd ||= actualEnd;
38
38
  if ((expectedIndent !== actualIndent) || endMatching) {
39
39
  if (expectedEnd === actualEnd) {
40
40
  endMatching = true;
package/lib/md007.mjs CHANGED
@@ -18,9 +18,9 @@ export default {
18
18
  "tags": [ "bullet", "ul", "indentation" ],
19
19
  "parser": "micromark",
20
20
  "function": function MD007(params, onError) {
21
- const indent = Number(params.config.indent || 2);
21
+ const indent = Number(params.config.indent ?? 2);
22
22
  const startIndented = !!params.config.start_indented;
23
- const startIndent = Number(params.config.start_indent || indent);
23
+ const startIndent = Number(params.config.start_indent ?? indent);
24
24
  const unorderedListNesting = new Map();
25
25
  let lastBlockQuotePrefix = null;
26
26
  const tokens = filterByTypesCached(unorderedListTypes);
package/lib/md009.mjs CHANGED
@@ -11,10 +11,9 @@ export default {
11
11
  "tags": [ "whitespace" ],
12
12
  "parser": "micromark",
13
13
  "function": function MD009(params, onError) {
14
- let brSpaces = params.config.br_spaces;
15
- brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces);
14
+ const brSpaces = Number(params.config.br_spaces ?? 2);
16
15
  const codeBlocks = params.config.code_blocks;
17
- const includeCode = (codeBlocks === undefined) ? false : !!codeBlocks;
16
+ const includeCode = (codeBlocks !== undefined) && !!codeBlocks;
18
17
  const listItemEmptyLines = !!params.config.list_item_empty_lines;
19
18
  const strict = !!params.config.strict;
20
19
  const codeBlockLineNumbers = new Set();
package/lib/md010.mjs CHANGED
@@ -14,7 +14,7 @@ export default {
14
14
  "parser": "micromark",
15
15
  "function": function MD010(params, onError) {
16
16
  const codeBlocks = params.config.code_blocks;
17
- const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks;
17
+ const includeCode = (codeBlocks === undefined) || !!codeBlocks;
18
18
  const ignoreCodeLanguages = new Set(
19
19
  (params.config.ignore_code_languages || [])
20
20
  .map((/** @type {void} */ language) => String(language).toLowerCase())
package/lib/md011.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  // @ts-check
2
2
 
3
+ import { re } from "re2js";
3
4
  import { addError, hasOverlap } from "../helpers/helpers.cjs";
4
5
  import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
5
6
  import { filterByTypesCached } from "./cache.mjs";
6
7
 
7
- /** @typedef {import("micromark-extension-math")} */
8
+ /** @typedef {typeof import("micromark-extension-math")} _ambient_ */
8
9
 
9
- const reversedLinkRe = /(^|[^\\])\(([^()]+)\)\[([^\]^][^\]]*)\](?!\()/g;
10
+ const reversedLinkRe = re`(^|[^\\])\(([^()]+)\)\[([^\]^][^\]]*)\]`;
10
11
 
11
12
  /** @type {import("markdownlint").Rule} */
12
13
  export default {
@@ -16,22 +17,24 @@ export default {
16
17
  "parser": "micromark",
17
18
  "function": function MD011(params, onError) {
18
19
  const ignoreBlockLineNumbers = new Set();
19
- for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "mathFlow" ])) {
20
+ for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow", "mathFlow" ])) {
20
21
  addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
21
22
  }
22
23
  const ignoreTexts = filterByTypesCached([ "codeText", "mathText" ]);
23
24
  for (const [ lineIndex, line ] of params.lines.entries()) {
24
25
  const lineNumber = lineIndex + 1;
25
26
  if (!ignoreBlockLineNumbers.has(lineNumber)) {
26
- let match = null;
27
- while ((match = reversedLinkRe.exec(line)) !== null) {
27
+ for (const match of reversedLinkRe.matchAll(line)) {
28
28
  const [ reversedLink, preChar, linkText, linkDestination ] = match;
29
+ const matchIndex = match.index ?? 0;
30
+ const matchLength = match[0].length;
29
31
  if (
32
+ (line[matchIndex + matchLength] !== "(") &&
30
33
  !linkText.endsWith("\\") &&
31
34
  !linkDestination.endsWith("\\")
32
35
  ) {
33
- const column = match.index + preChar.length + 1;
34
- const length = match[0].length - preChar.length;
36
+ const column = matchIndex + preChar.length + 1;
37
+ const length = matchLength - preChar.length;
35
38
  /** @type {import("../helpers/helpers.cjs").FileRange} */
36
39
  const range = { "startLine": lineNumber, "startColumn": column, "endLine": lineNumber, "endColumn": column + length - 1 };
37
40
  if (!ignoreTexts.some((ignoreText) => hasOverlap(ignoreText, range))) {
package/lib/md012.mjs CHANGED
@@ -11,7 +11,7 @@ export default {
11
11
  "tags": [ "whitespace", "blank_lines" ],
12
12
  "parser": "micromark",
13
13
  "function": function MD012(params, onError) {
14
- const maximum = Number(params.config.maximum || 1);
14
+ const maximum = Number(params.config.maximum ?? 1);
15
15
  const { lines } = params;
16
16
  const codeBlockLineNumbers = new Set();
17
17
  for (const codeBlock of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
package/lib/md013.mjs CHANGED
@@ -1,14 +1,18 @@
1
1
  // @ts-check
2
2
 
3
+ import { re } from "re2js";
3
4
  import { addErrorDetailIf } from "../helpers/helpers.cjs";
4
5
  import { filterByTypesCached, getReferenceLinkImageData } from "./cache.mjs";
5
6
  import { addRangeToSet, getDescendantsByType } from "../helpers/micromark-helpers.cjs";
6
7
 
8
+ /** @typedef {typeof import("micromark-extension-gfm-autolink-literal")} _ambient_ */
9
+ /** @typedef {typeof import("micromark-extension-gfm-table")} _ambient_2_ */
10
+
7
11
  // Regular expression for a line that is not wrappable
8
12
  const notWrappableRe = /^(?:[#>\s]*\s)?\S*$/;
9
13
 
10
- /** @typedef {import("micromark-extension-gfm-autolink-literal")} */
11
- /** @typedef {import("micromark-extension-gfm-table")} */
14
+ // Regular expression for the last run of non-whitespace
15
+ const lastRunRe = re`\S*$`;
12
16
 
13
17
  /** @type {import("markdownlint").Rule} */
14
18
  export default {
@@ -17,17 +21,17 @@ export default {
17
21
  "tags": [ "line_length" ],
18
22
  "parser": "micromark",
19
23
  "function": function MD013(params, onError) {
20
- const lineLength = Number(params.config.line_length || 80);
21
- const headingLineLength = Number(params.config.heading_line_length || lineLength);
22
- const codeLineLength = Number(params.config.code_block_line_length || lineLength);
24
+ const lineLength = Number(params.config.line_length ?? 80);
25
+ const headingLineLength = Number(params.config.heading_line_length ?? lineLength);
26
+ const codeLineLength = Number(params.config.code_block_line_length ?? lineLength);
23
27
  const strict = !!params.config.strict;
24
28
  const stern = !!params.config.stern;
25
29
  const codeBlocks = params.config.code_blocks;
26
- const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
30
+ const includeCodeBlocks = (codeBlocks === undefined) || !!codeBlocks;
27
31
  const tables = params.config.tables;
28
- const includeTables = (tables === undefined) ? true : !!tables;
32
+ const includeTables = (tables === undefined) || !!tables;
29
33
  const headings = params.config.headings;
30
- const includeHeadings = (headings === undefined) ? true : !!headings;
34
+ const includeHeadings = (headings === undefined) || !!headings;
31
35
  const headingLineNumbers = new Set();
32
36
  for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
33
37
  addRangeToSet(headingLineNumbers, heading.startLine, heading.endLine);
@@ -66,7 +70,7 @@ export default {
66
70
  const maxLength = inCode ? codeLineLength : (isHeading ? headingLineLength : lineLength);
67
71
  // If not strict/stern, the last run of non-whitespace is allowed to go
68
72
  // beyond the limit as long as it begins within the limit
69
- const text = (strict || stern) ? line : line.replace(/\S*$/u, "#");
73
+ const text = (strict || stern) ? line : lastRunRe.matcher(line).replaceFirst("#");
70
74
  if ((maxLength > 0) &&
71
75
  (includeCodeBlocks || !inCode) &&
72
76
  (includeTables || !inTable) &&
package/lib/md018.mjs CHANGED
@@ -13,7 +13,7 @@ export default {
13
13
  "function": function MD018(params, onError) {
14
14
  const { lines } = params;
15
15
  const ignoreBlockLineNumbers = new Set();
16
- for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow" ])) {
16
+ for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow", "mathFlow" ])) {
17
17
  addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
18
18
  }
19
19
  for (const [ lineIndex, line ] of lines.entries()) {
@@ -16,6 +16,7 @@ function validateHeadingSpaces(onError, heading, delta) {
16
16
  const { children, startLine, text } = heading;
17
17
  let index = (delta > 0) ? 0 : (children.length - 1);
18
18
  while (
19
+ // eslint-disable-next-line unicorn/no-computed-property-existence-check
19
20
  children[index] &&
20
21
  (children[index].type !== "atxHeadingSequence")
21
22
  ) {
package/lib/md020.mjs CHANGED
@@ -13,7 +13,7 @@ export default {
13
13
  "function": function MD020(params, onError) {
14
14
  const { lines } = params;
15
15
  const ignoreBlockLineNumbers = new Set();
16
- for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow" ])) {
16
+ for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow", "mathFlow" ])) {
17
17
  addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
18
18
  }
19
19
  for (const [ lineIndex, line ] of lines.entries()) {