markdownlint 0.41.1 → 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 (56) hide show
  1. package/CHANGELOG.md +18 -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 +76 -35
  6. package/doc/md010.md +0 -1
  7. package/doc/md024.md +1 -1
  8. package/doc/md043.md +47 -9
  9. package/doc/md051.md +19 -19
  10. package/doc/md055.md +6 -3
  11. package/doc/md060.md +3 -2
  12. package/helpers/README.md +2 -2
  13. package/helpers/helpers.cjs +16 -7
  14. package/helpers/micromark-helpers.cjs +7 -10
  15. package/helpers/package.json +1 -5
  16. package/lib/configuration-strict.d.ts +124 -112
  17. package/lib/constants.mjs +1 -1
  18. package/lib/defer-require.cjs +1 -0
  19. package/lib/exports.d.mts +29 -1
  20. package/lib/markdownit.cjs +8 -1
  21. package/lib/markdownlint.d.mts +337 -195
  22. package/lib/markdownlint.mjs +78 -85
  23. package/lib/md007.mjs +2 -2
  24. package/lib/md009.mjs +2 -3
  25. package/lib/md010.mjs +1 -1
  26. package/lib/md011.mjs +10 -7
  27. package/lib/md012.mjs +1 -1
  28. package/lib/md013.mjs +13 -9
  29. package/lib/md018.mjs +1 -1
  30. package/lib/md020.mjs +1 -1
  31. package/lib/md022.mjs +5 -6
  32. package/lib/md025.mjs +1 -1
  33. package/lib/md027.mjs +1 -1
  34. package/lib/md028.mjs +1 -2
  35. package/lib/md029.mjs +1 -2
  36. package/lib/md030.mjs +4 -4
  37. package/lib/md031.mjs +1 -1
  38. package/lib/md033.mjs +1 -1
  39. package/lib/md034.mjs +1 -1
  40. package/lib/md039.mjs +5 -1
  41. package/lib/md041.mjs +1 -1
  42. package/lib/md043.mjs +98 -44
  43. package/lib/md044.mjs +2 -2
  44. package/lib/md048.mjs +1 -3
  45. package/lib/md051.mjs +2 -3
  46. package/lib/md055.mjs +11 -1
  47. package/lib/md056.mjs +1 -1
  48. package/lib/md058.mjs +1 -1
  49. package/lib/md060.mjs +24 -4
  50. package/lib/micromark-parse.mjs +1 -1
  51. package/lib/resolve-module.d.cts +15 -10
  52. package/package.json +31 -34
  53. package/schema/.markdownlint.jsonc +54 -54
  54. package/schema/.markdownlint.yaml +54 -54
  55. package/schema/markdownlint-config-schema-strict.json +126 -118
  56. 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."
@@ -117,14 +118,15 @@ function validateRuleList(ruleList, synchronous) {
117
118
  * Remove front matter (if present at beginning of content).
118
119
  *
119
120
  * @param {string} content Markdown content.
120
- * @param {RegExp | null} frontMatter Regular expression to match front matter.
121
+ * @param {RE2JS | null} frontMatterRe2 Regular expression to match front matter.
121
122
  * @returns {RemoveFrontMatterResult} Trimmed content and front matter lines.
122
123
  */
123
- function removeFrontMatter(content, frontMatter) {
124
+ function removeFrontMatter(content, frontMatterRe2) {
124
125
  /** @type {string[]} */
125
126
  let frontMatterLines = [];
126
- if (frontMatter) {
127
- const frontMatterMatch = content.match(frontMatter);
127
+ if (frontMatterRe2) {
128
+ const frontMatterMatch = frontMatterRe2.exec(content);
129
+ // @ts-ignore
128
130
  if (frontMatterMatch && !frontMatterMatch.index) {
129
131
  const contentMatched = frontMatterMatch[0];
130
132
  content = content.slice(contentMatched.length);
@@ -199,7 +201,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
199
201
  let ruleDefaultEnable = true;
200
202
  /** @type {"error" | "warning"} */
201
203
  let ruleDefaultSeverity = "error";
202
- // eslint-disable-next-line unicorn/no-unused-array-method-return
204
+ // eslint-disable-next-line unicorn/no-unused-builtin-method-return
203
205
  Object.entries(config).every(([ key, value ]) => {
204
206
  if (key.toUpperCase() === "DEFAULT") {
205
207
  ruleDefaultEnable = !!value;
@@ -236,7 +238,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
236
238
  if (value instanceof Object) {
237
239
  /** @type {{ enabled?: boolean, severity?: "error" | "warning" }} */
238
240
  const valueObject = value;
239
- enabled = (valueObject.enabled === undefined) ? true : !!valueObject.enabled;
241
+ enabled = (valueObject.enabled === undefined) || !!valueObject.enabled;
240
242
  severity = (valueObject.severity === "warning") ? "warning" : "error";
241
243
  effectiveValue = Object.fromEntries(
242
244
  Object.entries(value).filter(
@@ -277,7 +279,6 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
277
279
  *
278
280
  * @param {Rule[]} ruleList List of rules.
279
281
  * @param {string[]} lines List of content lines.
280
- * @param {string[]} frontMatterLines List of front matter lines.
281
282
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
282
283
  * @param {Configuration} config Configuration object.
283
284
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
@@ -287,7 +288,6 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
287
288
  function getEnabledRulesPerLineNumber(
288
289
  ruleList,
289
290
  lines,
290
- frontMatterLines,
291
291
  noInlineConfig,
292
292
  config,
293
293
  configParsers,
@@ -297,7 +297,7 @@ function getEnabledRulesPerLineNumber(
297
297
  let enabledRules = new Map();
298
298
  /** @type {Map<string, boolean>} */
299
299
  let capturedRules = enabledRules;
300
- const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
300
+ const enabledRulesPerLineNumber = new Array(1);
301
301
  // Helper functions
302
302
  // eslint-disable-next-line jsdoc/require-jsdoc
303
303
  function handleInlineConfig(/** @type {string[]} */ input, /** @type {(act: string, par: string, ind: number) => void} */ forEachMatch, /** @type {(() => void)|undefined} */ forEachLine = undefined) {
@@ -373,8 +373,7 @@ function getEnabledRulesPerLineNumber(
373
373
  const disableLine = (action === "DISABLE-LINE");
374
374
  const disableNextLine = (action === "DISABLE-NEXT-LINE");
375
375
  if (disableLine || disableNextLine) {
376
- const nextLineNumber =
377
- frontMatterLines.length + lineNumber + (disableNextLine ? 1 : 0);
376
+ const nextLineNumber = lineNumber + (disableNextLine ? 1 : 0);
378
377
  enabledRulesPerLineNumber[nextLineNumber] =
379
378
  applyEnableDisable(
380
379
  action,
@@ -416,7 +415,7 @@ function getEnabledRulesPerLineNumber(
416
415
  * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
417
416
  * @param {Configuration} config Configuration object.
418
417
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
419
- * @param {RegExp | null} frontMatter Regular expression for front matter.
418
+ * @param {RE2JS | null} frontMatterRe2 Regular expression for front matter.
420
419
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
421
420
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
422
421
  * @param {boolean} synchronous Whether to execute synchronously.
@@ -431,7 +430,7 @@ function lintContent(
431
430
  markdownItFactory,
432
431
  config,
433
432
  configParsers,
434
- frontMatter,
433
+ frontMatterRe2,
435
434
  handleRuleFailures,
436
435
  noInlineConfig,
437
436
  synchronous,
@@ -441,17 +440,16 @@ function lintContent(
441
440
  const callbackError = (/** @type {any} */ error) => callback(error instanceof Error ? error : new Error(error));
442
441
  // Remove UTF-8 byte order marker (if present)
443
442
  // eslint-disable-next-line unicorn/prefer-unicode-code-point-escapes
444
- content = content.replace(/^\uFEFF/, "");
443
+ const fullContent = content.replace(/^\uFEFF/, "");
444
+ const fullContentLines = fullContent.split(helpers.newLineRe);
445
+ const fullContentNormalized = fullContentLines.join("\n");
445
446
  // Remove front matter
446
- const removeFrontMatterResult = removeFrontMatter(content, frontMatter);
447
- const { frontMatterLines } = removeFrontMatterResult;
448
- content = removeFrontMatterResult.content;
447
+ const { "content": markdownContentNormalized, frontMatterLines } = removeFrontMatter(fullContentNormalized, frontMatterRe2);
449
448
  // Get enabled rules per line (with HTML comments present)
450
449
  const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList, rulesSeverity } =
451
450
  getEnabledRulesPerLineNumber(
452
451
  ruleList,
453
- content.split(helpers.newLineRe),
454
- frontMatterLines,
452
+ fullContentLines,
455
453
  noInlineConfig,
456
454
  config,
457
455
  configParsers,
@@ -466,13 +464,12 @@ function lintContent(
466
464
  const customRulesPresent = (ruleList.length !== rules.length);
467
465
  // Parse content into parser tokens
468
466
  const micromarkTokens = needMicromarkTokens ?
469
- micromarkParse(content, { "freezeTokens": customRulesPresent }) :
467
+ micromarkParse(markdownContentNormalized, { "freezeTokens": customRulesPresent }) :
470
468
  [];
471
469
  // Hide the content of HTML comments from rules
472
- const preClearedContent = content;
473
- content = helpers.clearHtmlCommentText(content);
470
+ const markdownContentNormalizedSanitized = helpers.clearHtmlCommentText(markdownContentNormalized);
474
471
  // Parse content into lines and get markdown-it tokens
475
- const lines = content.split(helpers.newLineRe);
472
+ const lines = markdownContentNormalizedSanitized.split(helpers.newLineRe);
476
473
  // Function to run after fetching markdown-it tokens (when needed)
477
474
  const lintContentInternal = (/** @type {MarkdownItToken[]} */ markdownitTokens) => {
478
475
  // Create (frozen) parameters for rules
@@ -539,37 +536,45 @@ function lintContent(
539
536
  }
540
537
  // eslint-disable-next-line jsdoc/require-jsdoc
541
538
  function onError(/** @type {RuleOnErrorInfo} */ errorInfo) {
542
- if (!errorInfo ||
543
- !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) ||
544
550
  (errorInfo.lineNumber < 1) ||
545
- (errorInfo.lineNumber > lines.length)) {
551
+ (errorInfo.lineNumber > contentLines.length)) {
546
552
  throwError("lineNumber");
547
553
  }
548
- const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
554
+ const lineNumber = errorInfo.lineNumber + lineOffset;
549
555
  if (!enabledRulesPerLineNumber[lineNumber].get(ruleName)) {
550
556
  return;
551
557
  }
552
- if (errorInfo.detail &&
558
+ if ((errorInfo.detail !== undefined) &&
553
559
  !helpers.isString(errorInfo.detail)) {
554
560
  throwError("detail");
555
561
  }
556
- if (errorInfo.context &&
562
+ if ((errorInfo.context !== undefined) &&
557
563
  !helpers.isString(errorInfo.context)) {
558
564
  throwError("context");
559
565
  }
560
- if (errorInfo.information &&
566
+ if ((errorInfo.information !== undefined) &&
561
567
  !helpers.isUrl(errorInfo.information)) {
562
568
  throwError("information");
563
569
  }
564
- if (errorInfo.range &&
570
+ if ((errorInfo.range !== undefined) &&
565
571
  (!Array.isArray(errorInfo.range) ||
566
572
  (errorInfo.range.length !== 2) ||
567
573
  !helpers.isNumber(errorInfo.range[0]) ||
568
574
  (errorInfo.range[0] < 1) ||
569
575
  !helpers.isNumber(errorInfo.range[1]) ||
570
576
  (errorInfo.range[1] < 1) ||
571
- ((errorInfo.range[0] + errorInfo.range[1] - 1) >
572
- lines[errorInfo.lineNumber - 1].length))) {
577
+ ((errorInfo.range[0] + errorInfo.range[1] - 1) > contentLines[errorInfo.lineNumber - 1].length))) {
573
578
  throwError("range");
574
579
  }
575
580
  const fixInfo = errorInfo.fixInfo;
@@ -581,18 +586,16 @@ function lintContent(
581
586
  if (fixInfo.lineNumber !== undefined) {
582
587
  if ((!helpers.isNumber(fixInfo.lineNumber) ||
583
588
  (fixInfo.lineNumber < 1) ||
584
- (fixInfo.lineNumber > lines.length))) {
589
+ (fixInfo.lineNumber > contentLines.length))) {
585
590
  throwError("fixInfo.lineNumber");
586
591
  }
587
- cleanFixInfo.lineNumber =
588
- fixInfo.lineNumber + frontMatterLines.length;
592
+ cleanFixInfo.lineNumber = fixInfo.lineNumber + lineOffset;
589
593
  }
590
594
  const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
591
595
  if (fixInfo.editColumn !== undefined) {
592
596
  if ((!helpers.isNumber(fixInfo.editColumn) ||
593
597
  (fixInfo.editColumn < 1) ||
594
- (fixInfo.editColumn >
595
- lines[effectiveLineNumber - 1].length + 1))) {
598
+ (fixInfo.editColumn > contentLines[effectiveLineNumber - 1].length + 1))) {
596
599
  throwError("fixInfo.editColumn");
597
600
  }
598
601
  cleanFixInfo.editColumn = fixInfo.editColumn;
@@ -600,8 +603,7 @@ function lintContent(
600
603
  if (fixInfo.deleteCount !== undefined) {
601
604
  if ((!helpers.isNumber(fixInfo.deleteCount) ||
602
605
  (fixInfo.deleteCount < -1) ||
603
- (fixInfo.deleteCount >
604
- lines[effectiveLineNumber - 1].length))) {
606
+ (((fixInfo.editColumn ?? 1) + fixInfo.deleteCount - 1) > contentLines[effectiveLineNumber - 1].length))) {
605
607
  throwError("fixInfo.deleteCount");
606
608
  }
607
609
  cleanFixInfo.deleteCount = fixInfo.deleteCount;
@@ -688,7 +690,7 @@ function lintContent(
688
690
  if (!needMarkdownItTokens || synchronous) {
689
691
  // Need/able to call into markdown-it and lintContentInternal synchronously
690
692
  const markdownItTokens = needMarkdownItTokens ?
691
- requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), preClearedContent, lines) :
693
+ requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), markdownContentNormalized, lines) :
692
694
  [];
693
695
  lintContentInternal(markdownItTokens);
694
696
  } else {
@@ -699,7 +701,7 @@ function lintContent(
699
701
  // eslint-disable-next-line no-promise-executor-return
700
702
  new Promise((resolve) => resolve(markdownItFactory()))
701
703
  ]).then(([ markdownitCjs, markdownIt ]) => {
702
- const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, preClearedContent, lines);
704
+ const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, markdownContentNormalized, lines);
703
705
  lintContentInternal(markdownItTokens);
704
706
  }).catch(callbackError);
705
707
  }
@@ -714,7 +716,7 @@ function lintContent(
714
716
  * @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
715
717
  * @param {Configuration} config Configuration object.
716
718
  * @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
717
- * @param {RegExp | null} frontMatter Regular expression for front matter.
719
+ * @param {RE2JS | null} frontMatterRe2 Regular expression for front matter.
718
720
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
719
721
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
720
722
  * @param {FsLike} fs File system implementation.
@@ -729,7 +731,7 @@ function lintFile(
729
731
  markdownItFactory,
730
732
  config,
731
733
  configParsers,
732
- frontMatter,
734
+ frontMatterRe2,
733
735
  handleRuleFailures,
734
736
  noInlineConfig,
735
737
  fs,
@@ -737,23 +739,22 @@ function lintFile(
737
739
  callback) {
738
740
  // eslint-disable-next-line jsdoc/require-jsdoc
739
741
  function lintContentWrapper(/** @type {Error | null} */ err, /** @type {string} */ content) {
740
- if (err) {
741
- return callback(err);
742
- }
743
- return lintContent(
744
- ruleList,
745
- aliasToRuleNames,
746
- file,
747
- content,
748
- markdownItFactory,
749
- config,
750
- configParsers,
751
- frontMatter,
752
- handleRuleFailures,
753
- noInlineConfig,
754
- synchronous,
755
- callback
756
- );
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
+ );
757
758
  }
758
759
  // Make a/synchronous call to read file
759
760
  if (synchronous) {
@@ -807,9 +808,8 @@ function lintInput(options, synchronous, callback) {
807
808
  const stringsKeys = Object.keys(strings);
808
809
  const config = options.config || { "default": true };
809
810
  const configParsers = options.configParsers || undefined;
810
- const frontMatter = (options.frontMatter === undefined) ?
811
- helpers.frontMatterRe :
812
- options.frontMatter;
811
+ const frontMatterRe = (options.frontMatter === undefined) ? helpers.frontMatterRe : options.frontMatter;
812
+ const frontMatterRe2 = frontMatterRe && RE2JS.compile(RE2JS.translateRegExp(frontMatterRe));
813
813
  const handleRuleFailures = !!options.handleRuleFailures;
814
814
  const noInlineConfig = !!options.noInlineConfig;
815
815
  const markdownItFactory =
@@ -852,7 +852,7 @@ function lintInput(options, synchronous, callback) {
852
852
  markdownItFactory,
853
853
  config,
854
854
  configParsers,
855
- frontMatter,
855
+ frontMatterRe2,
856
856
  handleRuleFailures,
857
857
  noInlineConfig,
858
858
  fs,
@@ -870,7 +870,7 @@ function lintInput(options, synchronous, callback) {
870
870
  markdownItFactory,
871
871
  config,
872
872
  configParsers,
873
- frontMatter,
873
+ frontMatterRe2,
874
874
  handleRuleFailures,
875
875
  noInlineConfig,
876
876
  synchronous,
@@ -1029,8 +1029,8 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) {
1029
1029
  */
1030
1030
  function extendConfig(config, file, parsers, fs, callback) {
1031
1031
  const configExtends = config.extends;
1032
- if (configExtends) {
1033
- return resolveConfigExtends(
1032
+ return configExtends ?
1033
+ resolveConfigExtends(
1034
1034
  file,
1035
1035
  helpers.expandTildePath(configExtends, os),
1036
1036
  fs,
@@ -1052,9 +1052,8 @@ function extendConfig(config, file, parsers, fs, callback) {
1052
1052
  return callback(null, result);
1053
1053
  }
1054
1054
  )
1055
- );
1056
- }
1057
- return callback(null, config);
1055
+ ) :
1056
+ callback(null, config);
1058
1057
  }
1059
1058
 
1060
1059
  /**
@@ -1114,13 +1113,11 @@ export function readConfigAsync(file, parsers, fs, callback) {
1114
1113
  // Try to parse file
1115
1114
  // @ts-ignore
1116
1115
  const { config, message } = parseConfiguration(file, content, parsers);
1117
- if (!config) {
1116
+ return config ?
1118
1117
  // @ts-ignore
1119
- return callback(new Error(message));
1120
- }
1121
- // Extend configuration
1122
- // @ts-ignore
1123
- return extendConfig(config, file, parsers, fsLike, callback);
1118
+ extendConfig(config, file, parsers, fsLike, callback) :
1119
+ // @ts-ignore
1120
+ callback(new Error(message));
1124
1121
  });
1125
1122
  }
1126
1123
 
@@ -1411,6 +1408,7 @@ export function getVersion() {
1411
1408
  * Fix information for RuleOnError callback.
1412
1409
  *
1413
1410
  * @typedef {Object} RuleOnErrorInfo
1411
+ * @property {boolean} [frontMatter] True if applies to front matter.
1414
1412
  * @property {number} lineNumber Line number (1-based).
1415
1413
  * @property {string} [detail] Detail about the error.
1416
1414
  * @property {string} [context] Context for the error.
@@ -1451,12 +1449,7 @@ export function getVersion() {
1451
1449
  * @returns {MarkdownItBaseToken[]} Tokens.
1452
1450
  */
1453
1451
 
1454
- /**
1455
- * Instance of the markdown-it parser.
1456
- *
1457
- * @typedef MarkdownIt
1458
- * @property {MarkdownItParse} parse Method to parse input.
1459
- */
1452
+ /** @typedef {import("markdown-it").MarkdownIt} MarkdownIt */
1460
1453
 
1461
1454
  /**
1462
1455
  * Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
@@ -1490,7 +1483,7 @@ export function getVersion() {
1490
1483
  /**
1491
1484
  * Lint results.
1492
1485
  *
1493
- * @typedef {Object.<string, LintError[]>} LintResults
1486
+ * @typedef {{[k: string]: LintError[]}} LintResults
1494
1487
  */
1495
1488
 
1496
1489
  /**
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()) {
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()) {
package/lib/md022.mjs CHANGED
@@ -13,13 +13,13 @@ const getLinesFunction = (/** @type {any} */ linesParam) => {
13
13
  if (Array.isArray(linesParam)) {
14
14
  // eslint-disable-next-line unicorn/no-unreadable-new-expression
15
15
  const linesArray = new Array(6).fill(defaultLines);
16
- for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) {
16
+ for (const [ index, value ] of linesParam.slice(0, 6).entries()) {
17
17
  linesArray[index] = value;
18
18
  }
19
19
  return (/** @type {MicromarkToken} */ heading) => linesArray[getHeadingLevel(heading) - 1];
20
20
  }
21
21
  // Coerce linesParam to a number
22
- const lines = (linesParam === undefined) ? defaultLines : Number(linesParam);
22
+ const lines = Number(linesParam ?? defaultLines);
23
23
  return () => lines;
24
24
  };
25
25
 
@@ -27,10 +27,9 @@ const getLine = (/** @type {readonly string[]} */ lines, /** @type {number} */ i
27
27
  if ((index >= 0) && (index < lines.length)) {
28
28
  return lines[index];
29
29
  }
30
- if (includeFrontMatter && (frontMatterLines.length > 0) && (index < 0) && (index >= -frontMatterLines.length)) {
31
- return frontMatterLines[frontMatterLines.length + index];
32
- }
33
- return "";
30
+ return (includeFrontMatter && (frontMatterLines.length > 0) && (index < 0) && (index >= -frontMatterLines.length)) ?
31
+ frontMatterLines[frontMatterLines.length + index] :
32
+ "";
34
33
  };
35
34
 
36
35
  /** @type {import("markdownlint").Rule} */
package/lib/md025.mjs CHANGED
@@ -11,7 +11,7 @@ export default {
11
11
  "tags": [ "headings" ],
12
12
  "parser": "micromark",
13
13
  "function": function MD025(params, onError) {
14
- const level = Number(params.config.level || 1);
14
+ const level = Number(params.config.level ?? 1);
15
15
  const { tokens } = params.parsers.micromark;
16
16
  const matchingHeadings = filterByTypesCached([ "atxHeading", "setextHeading" ])
17
17
  .filter((heading) => (level === getHeadingLevel(heading)) && !isDocfxTab(heading));
package/lib/md027.mjs CHANGED
@@ -15,7 +15,7 @@ export default {
15
15
  "parser": "micromark",
16
16
  "function": function MD027(params, onError) {
17
17
  const listItems = params.config.list_items;
18
- const includeListItems = (listItems === undefined) ? true : !!listItems;
18
+ const includeListItems = (listItems === undefined) || !!listItems;
19
19
  const { tokens } = params.parsers.micromark;
20
20
  for (const token of filterByTypesCached([ "linePrefix" ])) {
21
21
  const parent = token.parent;
package/lib/md028.mjs CHANGED
@@ -16,8 +16,7 @@ export default {
16
16
  const errorLineNumbers = [];
17
17
  const siblings = token.parent?.children || params.parsers.micromark.tokens;
18
18
  for (let i = siblings.indexOf(token) + 1; i < siblings.length; i++) {
19
- const sibling = siblings[i];
20
- const { startLine, type } = sibling;
19
+ const { startLine, type } = siblings[i];
21
20
  if (type === "lineEndingBlank") {
22
21
  // Possible blank between blockquotes
23
22
  errorLineNumbers.push(startLine);
package/lib/md029.mjs CHANGED
@@ -18,8 +18,7 @@ const listStyles = Object.keys(listStyleExamples);
18
18
  * @returns {{startColumn: number, endColumn: number, text: string, number: number}} List item value column and text.
19
19
  */
20
20
  function getOrderedListItemValue(listItemPrefix) {
21
- const listItemValue = getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0];
22
- const { startColumn, endColumn, text } = listItemValue;
21
+ const { startColumn, endColumn, text } = getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0];
23
22
  return {
24
23
  startColumn,
25
24
  endColumn,