markdownlint 0.34.0 → 0.36.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 (90) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +97 -45
  3. package/demo/markdownlint-browser.js +2186 -2164
  4. package/doc/CustomRules.md +66 -255
  5. package/doc/Rules.md +102 -29
  6. package/doc/md003.md +2 -2
  7. package/doc/md004.md +4 -3
  8. package/doc/md034.md +5 -0
  9. package/doc/md035.md +3 -8
  10. package/doc/md044.md +10 -0
  11. package/doc/md046.md +2 -2
  12. package/doc/md048.md +3 -2
  13. package/doc/md049.md +4 -3
  14. package/doc/md050.md +4 -4
  15. package/doc/md051.md +11 -3
  16. package/doc/md055.md +3 -2
  17. package/doc/md058.md +48 -0
  18. package/helpers/README.md +2 -45
  19. package/helpers/helpers.js +115 -607
  20. package/helpers/micromark-helpers.cjs +296 -0
  21. package/helpers/micromark-parse.cjs +313 -0
  22. package/helpers/package.json +5 -2
  23. package/helpers/shared.js +6 -0
  24. package/helpers/test.cjs +28 -0
  25. package/lib/cache.js +62 -13
  26. package/lib/configuration-strict.d.ts +1199 -0
  27. package/lib/configuration.d.ts +3 -1173
  28. package/lib/constants.js +3 -2
  29. package/lib/markdownit.cjs +172 -0
  30. package/lib/markdownlint.d.ts +103 -56
  31. package/lib/markdownlint.js +180 -120
  32. package/lib/md001.js +15 -9
  33. package/lib/md003.js +14 -9
  34. package/lib/md004.js +55 -48
  35. package/lib/md005.js +4 -13
  36. package/lib/md007.js +7 -11
  37. package/lib/md009.js +50 -44
  38. package/lib/md010.js +52 -33
  39. package/lib/md011.js +32 -23
  40. package/lib/md012.js +17 -9
  41. package/lib/md013.js +44 -38
  42. package/lib/md014.js +31 -38
  43. package/lib/md018.js +17 -9
  44. package/lib/md019-md021.js +80 -0
  45. package/lib/md020.js +14 -8
  46. package/lib/md022.js +13 -29
  47. package/lib/md023.js +22 -23
  48. package/lib/md024.js +12 -9
  49. package/lib/md025.js +15 -10
  50. package/lib/md026.js +2 -7
  51. package/lib/md027.js +7 -12
  52. package/lib/md028.js +3 -8
  53. package/lib/md029.js +40 -34
  54. package/lib/md030.js +4 -10
  55. package/lib/md031.js +45 -23
  56. package/lib/md032.js +33 -31
  57. package/lib/md033.js +3 -8
  58. package/lib/md034.js +21 -34
  59. package/lib/md035.js +2 -7
  60. package/lib/md036.js +19 -24
  61. package/lib/md037.js +3 -8
  62. package/lib/md038.js +11 -22
  63. package/lib/md039.js +64 -54
  64. package/lib/md040.js +13 -20
  65. package/lib/md041.js +5 -5
  66. package/lib/md042.js +44 -38
  67. package/lib/md043.js +20 -14
  68. package/lib/md044.js +34 -36
  69. package/lib/md045.js +5 -10
  70. package/lib/md046.js +2 -8
  71. package/lib/md048.js +30 -23
  72. package/lib/md049-md050.js +22 -12
  73. package/lib/md051.js +20 -28
  74. package/lib/md052.js +2 -2
  75. package/lib/md053.js +7 -6
  76. package/lib/md054.js +16 -23
  77. package/lib/md055.js +41 -49
  78. package/lib/md056.js +32 -33
  79. package/lib/md058.js +58 -0
  80. package/lib/rules.js +11 -4
  81. package/package.json +23 -20
  82. package/schema/.markdownlint.jsonc +58 -52
  83. package/schema/.markdownlint.yaml +56 -51
  84. package/schema/ValidatingConfiguration.md +6 -8
  85. package/schema/markdownlint-config-schema-strict.json +1841 -0
  86. package/schema/markdownlint-config-schema.json +141 -109
  87. package/style/prettier.json +1 -0
  88. package/helpers/micromark.cjs +0 -445
  89. package/lib/md019.js +0 -42
  90. package/lib/md021.js +0 -65
@@ -4,9 +4,8 @@
4
4
 
5
5
  const path = require("node:path");
6
6
  const { promisify } = require("node:util");
7
- const markdownit = require("markdown-it");
8
- const micromark = require("../helpers/micromark.cjs");
9
- // const { deprecatedRuleNames } = require("./constants");
7
+ const micromark = require("../helpers/micromark-parse.cjs");
8
+ const { version } = require("./constants");
10
9
  const rules = require("./rules");
11
10
  const helpers = require("../helpers");
12
11
  const cache = require("./cache");
@@ -32,7 +31,7 @@ function validateRuleList(ruleList, synchronous) {
32
31
  const allIds = {};
33
32
  for (const [ index, rule ] of ruleList.entries()) {
34
33
  const customIndex = index - rules.length;
35
- // eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc
34
+ // eslint-disable-next-line jsdoc/require-jsdoc
36
35
  function newError(property, value) {
37
36
  return new Error(
38
37
  `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`);
@@ -59,7 +58,7 @@ function validateRuleList(ruleList, synchronous) {
59
58
  !result &&
60
59
  (rule.parser !== undefined) &&
61
60
  (rule.parser !== "markdownit") &&
62
- !((customIndex < 0) && (rule.parser === "micromark")) &&
61
+ (rule.parser !== "micromark") &&
63
62
  (rule.parser !== "none")
64
63
  ) {
65
64
  result = newError("parser", rule.parser);
@@ -198,83 +197,6 @@ function removeFrontMatter(content, frontMatter) {
198
197
  };
199
198
  }
200
199
 
201
- /**
202
- * Freeze all freeze-able members of a token and its children.
203
- *
204
- * @param {MarkdownItToken} token A markdown-it token.
205
- * @returns {void}
206
- */
207
- function freezeToken(token) {
208
- if (token.attrs) {
209
- for (const attr of token.attrs) {
210
- Object.freeze(attr);
211
- }
212
- Object.freeze(token.attrs);
213
- }
214
- if (token.children) {
215
- for (const child of token.children) {
216
- freezeToken(child);
217
- }
218
- Object.freeze(token.children);
219
- }
220
- if (token.map) {
221
- Object.freeze(token.map);
222
- }
223
- Object.freeze(token);
224
- }
225
-
226
- /**
227
- * Annotate tokens with line/lineNumber and freeze them.
228
- *
229
- * @param {MarkdownItToken[]} tokens Array of markdown-it tokens.
230
- * @param {string[]} lines Lines of Markdown content.
231
- * @returns {void}
232
- */
233
- function annotateAndFreezeTokens(tokens, lines) {
234
- let trMap = null;
235
- for (const token of tokens) {
236
- // Provide missing maps for table content
237
- if (token.type === "tr_open") {
238
- trMap = token.map;
239
- } else if (token.type === "tr_close") {
240
- trMap = null;
241
- }
242
- if (!token.map && trMap) {
243
- token.map = [ ...trMap ];
244
- }
245
- // Update token metadata
246
- if (token.map) {
247
- token.line = lines[token.map[0]];
248
- token.lineNumber = token.map[0] + 1;
249
- // Trim bottom of token to exclude whitespace lines
250
- while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
251
- token.map[1]--;
252
- }
253
- }
254
- // Annotate children with lineNumber
255
- if (token.children) {
256
- const codeSpanExtraLines = [];
257
- if (token.children.some((child) => child.type === "code_inline")) {
258
- helpers.forEachInlineCodeSpan(token.content, (code) => {
259
- codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1);
260
- });
261
- }
262
- let lineNumber = token.lineNumber;
263
- for (const child of token.children) {
264
- child.lineNumber = lineNumber;
265
- child.line = lines[lineNumber - 1];
266
- if ((child.type === "softbreak") || (child.type === "hardbreak")) {
267
- lineNumber++;
268
- } else if (child.type === "code_inline") {
269
- lineNumber += codeSpanExtraLines.shift();
270
- }
271
- }
272
- }
273
- freezeToken(token);
274
- }
275
- Object.freeze(tokens);
276
- }
277
-
278
200
  /**
279
201
  * Map rule names/tags to canonical rule name.
280
202
  *
@@ -507,10 +429,18 @@ function getEnabledRulesPerLineNumber(
507
429
  handleInlineConfig(lines, enableDisableFile);
508
430
  handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
509
431
  handleInlineConfig(lines, disableLineNextLine);
432
+ // Create the list of rules that are used at least once
433
+ const enabledRuleList = [];
434
+ for (const [ index, ruleName ] of allRuleNames.entries()) {
435
+ if (enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine[ruleName])) {
436
+ enabledRuleList.push(ruleList[index]);
437
+ }
438
+ }
510
439
  // Return results
511
440
  return {
512
441
  effectiveConfig,
513
- enabledRulesPerLineNumber
442
+ enabledRulesPerLineNumber,
443
+ enabledRuleList
514
444
  };
515
445
  }
516
446
 
@@ -522,7 +452,7 @@ function getEnabledRulesPerLineNumber(
522
452
  * names.
523
453
  * @param {string} name Identifier for the content.
524
454
  * @param {string} content Markdown content.
525
- * @param {Object} md Instance of markdown-it.
455
+ * @param {Plugin[]} markdownItPlugins Additional plugins.
526
456
  * @param {Configuration} config Configuration object.
527
457
  * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
528
458
  * @param {RegExp | null} frontMatter Regular expression for front matter.
@@ -537,7 +467,7 @@ function lintContent(
537
467
  aliasToRuleNames,
538
468
  name,
539
469
  content,
540
- md,
470
+ markdownItPlugins,
541
471
  config,
542
472
  configParsers,
543
473
  frontMatter,
@@ -552,7 +482,7 @@ function lintContent(
552
482
  const { frontMatterLines } = removeFrontMatterResult;
553
483
  content = removeFrontMatterResult.content;
554
484
  // Get enabled rules per line (with HTML comments present)
555
- const { effectiveConfig, enabledRulesPerLineNumber } =
485
+ const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList } =
556
486
  getEnabledRulesPerLineNumber(
557
487
  ruleList,
558
488
  content.split(helpers.newLineRe),
@@ -562,14 +492,23 @@ function lintContent(
562
492
  configParsers,
563
493
  aliasToRuleNames
564
494
  );
495
+ const needMarkdownItTokens = enabledRuleList.some(
496
+ (rule) => (rule.parser === "markdownit") || (rule.parser === undefined)
497
+ );
498
+ const customRulesPresent = (ruleList.length !== rules.length);
565
499
  // Parse content into parser tokens
566
- const markdownitTokens = md.parse(content, {});
567
- const micromarkTokens = micromark.parse(content);
500
+ const micromarkTokens = micromark.parse(
501
+ content,
502
+ { "freezeTokens": customRulesPresent }
503
+ );
568
504
  // Hide the content of HTML comments from rules
505
+ const preClearedContent = content;
569
506
  content = helpers.clearHtmlCommentText(content);
570
- // Parse content into lines and update markdown-it tokens
507
+ // Parse content into lines and get markdown-it tokens
571
508
  const lines = content.split(helpers.newLineRe);
572
- annotateAndFreezeTokens(markdownitTokens, lines);
509
+ const markdownitTokens = needMarkdownItTokens ?
510
+ require("./markdownit.cjs").getMarkdownItTokens(markdownItPlugins, preClearedContent, lines) :
511
+ [];
573
512
  // Create (frozen) parameters for rules
574
513
  /** @type {MarkdownParsers} */
575
514
  // @ts-ignore
@@ -590,23 +529,14 @@ function lintContent(
590
529
  const parsersNone = Object.freeze({});
591
530
  const paramsBase = {
592
531
  name,
593
- "parsers": parsersMarkdownIt,
532
+ version,
594
533
  "lines": Object.freeze(lines),
595
534
  "frontMatterLines": Object.freeze(frontMatterLines)
596
535
  };
597
- const lineMetadata =
598
- helpers.getLineMetadata(paramsBase);
599
- const codeBlockAndSpanRanges =
600
- helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata);
601
- const flattenedLists =
602
- helpers.flattenLists(markdownitTokens);
603
- const referenceLinkImageData =
604
- helpers.getReferenceLinkImageData(micromarkTokens);
605
- cache.set({
606
- codeBlockAndSpanRanges,
607
- flattenedLists,
608
- lineMetadata,
609
- referenceLinkImageData
536
+ cache.initialize({
537
+ ...paramsBase,
538
+ "parsers": parsersMicromark,
539
+ "config": null
610
540
  });
611
541
  // Function to run for each rule
612
542
  let results = [];
@@ -800,8 +730,8 @@ function lintContent(
800
730
  return results;
801
731
  }
802
732
  // Run all rules
803
- const ruleListAsync = ruleList.filter((rule) => rule.asynchronous);
804
- const ruleListSync = ruleList.filter((rule) => !rule.asynchronous);
733
+ const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
734
+ const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
805
735
  const ruleListAsyncFirst = [
806
736
  ...ruleListAsync,
807
737
  ...ruleListSync
@@ -821,7 +751,7 @@ function lintContent(
821
751
  } catch (error) {
822
752
  callbackError(error);
823
753
  } finally {
824
- cache.clear();
754
+ cache.initialize();
825
755
  }
826
756
  }
827
757
 
@@ -832,7 +762,7 @@ function lintContent(
832
762
  * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
833
763
  * names.
834
764
  * @param {string} file Path of file to lint.
835
- * @param {Object} md Instance of markdown-it.
765
+ * @param {Plugin[]} markdownItPlugins Additional plugins.
836
766
  * @param {Configuration} config Configuration object.
837
767
  * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
838
768
  * @param {RegExp | null} frontMatter Regular expression for front matter.
@@ -848,7 +778,7 @@ function lintFile(
848
778
  ruleList,
849
779
  aliasToRuleNames,
850
780
  file,
851
- md,
781
+ markdownItPlugins,
852
782
  config,
853
783
  configParsers,
854
784
  frontMatter,
@@ -868,7 +798,7 @@ function lintFile(
868
798
  aliasToRuleNames,
869
799
  file,
870
800
  content,
871
- md,
801
+ markdownItPlugins,
872
802
  config,
873
803
  configParsers,
874
804
  frontMatter,
@@ -928,17 +858,14 @@ function lintInput(options, synchronous, callback) {
928
858
  const config = options.config || { "default": true };
929
859
  const configParsers = options.configParsers || null;
930
860
  const frontMatter = (options.frontMatter === undefined) ?
931
- helpers.frontMatterRe : options.frontMatter;
861
+ helpers.frontMatterRe :
862
+ options.frontMatter;
932
863
  const handleRuleFailures = !!options.handleRuleFailures;
933
864
  const noInlineConfig = !!options.noInlineConfig;
934
865
  const resultVersion = (options.resultVersion === undefined) ?
935
- 3 : options.resultVersion;
936
- const md = markdownit({ "html": true });
866
+ 3 :
867
+ options.resultVersion;
937
868
  const markdownItPlugins = options.markdownItPlugins || [];
938
- for (const plugin of markdownItPlugins) {
939
- // @ts-ignore
940
- md.use(...plugin);
941
- }
942
869
  const fs = options.fs || require("node:fs");
943
870
  const aliasToRuleNames = mapAliasToRuleNames(ruleList);
944
871
  const results = newResults(ruleList);
@@ -970,7 +897,7 @@ function lintInput(options, synchronous, callback) {
970
897
  ruleList,
971
898
  aliasToRuleNames,
972
899
  currentItem,
973
- md,
900
+ markdownItPlugins,
974
901
  config,
975
902
  configParsers,
976
903
  frontMatter,
@@ -989,7 +916,7 @@ function lintInput(options, synchronous, callback) {
989
916
  aliasToRuleNames,
990
917
  currentItem,
991
918
  strings[currentItem] || "",
992
- md,
919
+ markdownItPlugins,
993
920
  config,
994
921
  configParsers,
995
922
  frontMatter,
@@ -1281,13 +1208,126 @@ function readConfigSync(file, parsers, fs) {
1281
1208
  return config;
1282
1209
  }
1283
1210
 
1211
+ /**
1212
+ * Normalizes the fields of a RuleOnErrorFixInfo instance.
1213
+ *
1214
+ * @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
1215
+ * @param {number} [lineNumber] Line number.
1216
+ * @returns {RuleOnErrorFixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
1217
+ */
1218
+ function normalizeFixInfo(fixInfo, lineNumber = 0) {
1219
+ return {
1220
+ "lineNumber": fixInfo.lineNumber || lineNumber,
1221
+ "editColumn": fixInfo.editColumn || 1,
1222
+ "deleteCount": fixInfo.deleteCount || 0,
1223
+ "insertText": fixInfo.insertText || ""
1224
+ };
1225
+ }
1226
+
1227
+ /**
1228
+ * Applies the specified fix to a Markdown content line.
1229
+ *
1230
+ * @param {string} line Line of Markdown content.
1231
+ * @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
1232
+ * @param {string} [lineEnding] Line ending to use.
1233
+ * @returns {string | null} Fixed content or null if deleted.
1234
+ */
1235
+ function applyFix(line, fixInfo, lineEnding = "\n") {
1236
+ const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo);
1237
+ const editIndex = editColumn - 1;
1238
+ return (deleteCount === -1) ?
1239
+ null :
1240
+ line.slice(0, editIndex) + insertText.replace(/\n/g, lineEnding) + line.slice(editIndex + deleteCount);
1241
+ }
1242
+
1243
+ /**
1244
+ * Applies as many of the specified fixes as possible to Markdown content.
1245
+ *
1246
+ * @param {string} input Lines of Markdown content.
1247
+ * @param {RuleOnErrorInfo[]} errors RuleOnErrorInfo instances.
1248
+ * @returns {string} Fixed content.
1249
+ */
1250
+ function applyFixes(input, errors) {
1251
+ const lineEnding = helpers.getPreferredLineEnding(input, require("node:os"));
1252
+ const lines = input.split(helpers.newLineRe);
1253
+ // Normalize fixInfo objects
1254
+ let fixInfos = errors
1255
+ .filter((error) => error.fixInfo)
1256
+ // @ts-ignore
1257
+ .map((error) => normalizeFixInfo(error.fixInfo, error.lineNumber));
1258
+ // Sort bottom-to-top, line-deletes last, right-to-left, long-to-short
1259
+ fixInfos.sort((a, b) => {
1260
+ const aDeletingLine = (a.deleteCount === -1);
1261
+ const bDeletingLine = (b.deleteCount === -1);
1262
+ return (
1263
+ (b.lineNumber - a.lineNumber) ||
1264
+ (aDeletingLine ? 1 : (bDeletingLine ? -1 : 0)) ||
1265
+ (b.editColumn - a.editColumn) ||
1266
+ (b.insertText.length - a.insertText.length)
1267
+ );
1268
+ });
1269
+ // Remove duplicate entries (needed for following collapse step)
1270
+ // eslint-disable-next-line jsdoc/valid-types
1271
+ /** @type RuleOnErrorFixInfo */
1272
+ let lastFixInfo = {};
1273
+ fixInfos = fixInfos.filter((fixInfo) => {
1274
+ const unique = (
1275
+ (fixInfo.lineNumber !== lastFixInfo.lineNumber) ||
1276
+ (fixInfo.editColumn !== lastFixInfo.editColumn) ||
1277
+ (fixInfo.deleteCount !== lastFixInfo.deleteCount) ||
1278
+ (fixInfo.insertText !== lastFixInfo.insertText)
1279
+ );
1280
+ lastFixInfo = fixInfo;
1281
+ return unique;
1282
+ });
1283
+ // Collapse insert/no-delete and no-insert/delete for same line/column
1284
+ lastFixInfo = {
1285
+ "lineNumber": -1
1286
+ };
1287
+ for (const fixInfo of fixInfos) {
1288
+ if (
1289
+ (fixInfo.lineNumber === lastFixInfo.lineNumber) &&
1290
+ (fixInfo.editColumn === lastFixInfo.editColumn) &&
1291
+ !fixInfo.insertText &&
1292
+ (fixInfo.deleteCount > 0) &&
1293
+ lastFixInfo.insertText &&
1294
+ !lastFixInfo.deleteCount) {
1295
+ fixInfo.insertText = lastFixInfo.insertText;
1296
+ lastFixInfo.lineNumber = 0;
1297
+ }
1298
+ lastFixInfo = fixInfo;
1299
+ }
1300
+ fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber);
1301
+ // Apply all (remaining/updated) fixes
1302
+ let lastLineIndex = -1;
1303
+ let lastEditIndex = -1;
1304
+ for (const fixInfo of fixInfos) {
1305
+ const { lineNumber, editColumn, deleteCount } = fixInfo;
1306
+ const lineIndex = lineNumber - 1;
1307
+ const editIndex = editColumn - 1;
1308
+ if (
1309
+ (lineIndex !== lastLineIndex) ||
1310
+ (deleteCount === -1) ||
1311
+ ((editIndex + deleteCount) <=
1312
+ (lastEditIndex - ((deleteCount > 0) ? 0 : 1)))
1313
+ ) {
1314
+ // @ts-ignore
1315
+ lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
1316
+ }
1317
+ lastLineIndex = lineIndex;
1318
+ lastEditIndex = editIndex;
1319
+ }
1320
+ // Return corrected input
1321
+ return lines.filter((line) => line !== null).join(lineEnding);
1322
+ }
1323
+
1284
1324
  /**
1285
1325
  * Gets the (semantic) version of the library.
1286
1326
  *
1287
1327
  * @returns {string} SemVer string.
1288
1328
  */
1289
1329
  function getVersion() {
1290
- return require("./constants").version;
1330
+ return version;
1291
1331
  }
1292
1332
 
1293
1333
  // Export a/synchronous/Promise APIs
@@ -1300,6 +1340,8 @@ markdownlint.promises = {
1300
1340
  "extendConfig": extendConfigPromise,
1301
1341
  "readConfig": readConfigPromise
1302
1342
  };
1343
+ markdownlint.applyFix = applyFix;
1344
+ markdownlint.applyFixes = applyFixes;
1303
1345
  module.exports = markdownlint;
1304
1346
 
1305
1347
  // Type declarations
@@ -1324,6 +1366,7 @@ module.exports = markdownlint;
1324
1366
  * @property {readonly string[]} lines File/string lines.
1325
1367
  * @property {readonly string[]} frontMatterLines Front matter lines.
1326
1368
  * @property {RuleConfiguration} config Rule configuration.
1369
+ * @property {string} version Version of the markdownlint library.
1327
1370
  */
1328
1371
 
1329
1372
  /* eslint-enable jsdoc/valid-types */
@@ -1417,6 +1460,16 @@ module.exports = markdownlint;
1417
1460
  * @property {string} [insertText] Text to insert (after deleting).
1418
1461
  */
1419
1462
 
1463
+ /**
1464
+ * RuleOnErrorInfo with all optional properties present.
1465
+ *
1466
+ * @typedef {Object} RuleOnErrorFixInfoNormalized
1467
+ * @property {number} lineNumber Line number (1-based).
1468
+ * @property {number} editColumn Column of the fix (1-based).
1469
+ * @property {number} deleteCount Count of characters to delete.
1470
+ * @property {string} insertText Text to insert (after deleting).
1471
+ */
1472
+
1420
1473
  /**
1421
1474
  * Rule definition.
1422
1475
  *
@@ -1517,6 +1570,13 @@ module.exports = markdownlint;
1517
1570
  * @typedef {import("./configuration").Configuration} Configuration
1518
1571
  */
1519
1572
 
1573
+ /**
1574
+ * Configuration object for linting rules strictly. For the JSON schema, see
1575
+ * {@link ../schema/markdownlint-config-schema-strict.json}.
1576
+ *
1577
+ * @typedef {import("./configuration-strict").ConfigurationStrict} ConfigurationStrict
1578
+ */
1579
+
1520
1580
  /**
1521
1581
  * Rule configuration object.
1522
1582
  *
package/lib/md001.js CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, filterTokens } = require("../helpers");
5
+ const { addErrorDetailIf } = require("../helpers");
6
+ const { getHeadingLevel } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
6
8
 
7
9
  // eslint-disable-next-line jsdoc/valid-types
8
10
  /** @type import("./markdownlint").Rule */
@@ -10,16 +12,20 @@ module.exports = {
10
12
  "names": [ "MD001", "heading-increment" ],
11
13
  "description": "Heading levels should only increment by one level at a time",
12
14
  "tags": [ "headings" ],
13
- "parser": "markdownit",
15
+ "parser": "micromark",
14
16
  "function": function MD001(params, onError) {
15
- let prevLevel = 0;
16
- filterTokens(params, "heading_open", function forToken(token) {
17
- const level = Number.parseInt(token.tag.slice(1), 10);
18
- if (prevLevel && (level > prevLevel)) {
19
- addErrorDetailIf(onError, token.lineNumber,
20
- "h" + (prevLevel + 1), "h" + level);
17
+ let prevLevel = Number.MAX_SAFE_INTEGER;
18
+ for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
19
+ const level = getHeadingLevel(heading);
20
+ if (level > prevLevel) {
21
+ addErrorDetailIf(
22
+ onError,
23
+ heading.startLine,
24
+ `h${prevLevel + 1}`,
25
+ `h${level}`
26
+ );
21
27
  }
22
28
  prevLevel = level;
23
- });
29
+ }
24
30
  }
25
31
  };
package/lib/md003.js CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, filterTokens, headingStyleFor } =
6
- require("../helpers");
5
+ const { addErrorDetailIf } = require("../helpers");
6
+ const { getHeadingLevel, getHeadingStyle } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
7
8
 
8
9
  // eslint-disable-next-line jsdoc/valid-types
9
10
  /** @type import("./markdownlint").Rule */
@@ -11,16 +12,16 @@ module.exports = {
11
12
  "names": [ "MD003", "heading-style" ],
12
13
  "description": "Heading style",
13
14
  "tags": [ "headings" ],
14
- "parser": "markdownit",
15
+ "parser": "micromark",
15
16
  "function": function MD003(params, onError) {
16
17
  let style = String(params.config.style || "consistent");
17
- filterTokens(params, "heading_open", function forToken(token) {
18
- const styleForToken = headingStyleFor(token);
18
+ for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
19
+ const styleForToken = getHeadingStyle(heading);
19
20
  if (style === "consistent") {
20
21
  style = styleForToken;
21
22
  }
22
23
  if (styleForToken !== style) {
23
- const h12 = /h[12]/.test(token.tag);
24
+ const h12 = getHeadingLevel(heading) <= 2;
24
25
  const setextWithAtx =
25
26
  (style === "setext_with_atx") &&
26
27
  ((h12 && (styleForToken === "setext")) ||
@@ -36,10 +37,14 @@ module.exports = {
36
37
  } else if (style === "setext_with_atx_closed") {
37
38
  expected = h12 ? "setext" : "atx_closed";
38
39
  }
39
- addErrorDetailIf(onError, token.lineNumber,
40
- expected, styleForToken);
40
+ addErrorDetailIf(
41
+ onError,
42
+ heading.startLine,
43
+ expected,
44
+ styleForToken
45
+ );
41
46
  }
42
47
  }
43
- });
48
+ }
44
49
  }
45
50
  };
package/lib/md004.js CHANGED
@@ -2,11 +2,16 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, listItemMarkerRe, unorderedListStyleFor } =
6
- require("../helpers");
7
- const { flattenedLists } = require("./cache");
5
+ const { addErrorDetailIf } = require("../helpers");
6
+ const { getDescendantsByType, getParentOfType } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
8
 
9
- const expectedStyleToMarker = {
9
+ const markerToStyle = {
10
+ "-": "dash",
11
+ "+": "plus",
12
+ "*": "asterisk"
13
+ };
14
+ const styleToMarker = {
10
15
  "dash": "-",
11
16
  "plus": "+",
12
17
  "asterisk": "*"
@@ -16,7 +21,13 @@ const differentItemStyle = {
16
21
  "plus": "asterisk",
17
22
  "asterisk": "dash"
18
23
  };
19
- const validStyles = Object.keys(expectedStyleToMarker);
24
+ const validStyles = new Set([
25
+ "asterisk",
26
+ "consistent",
27
+ "dash",
28
+ "plus",
29
+ "sublist"
30
+ ]);
20
31
 
21
32
  // eslint-disable-next-line jsdoc/valid-types
22
33
  /** @type import("./markdownlint").Rule */
@@ -24,55 +35,51 @@ module.exports = {
24
35
  "names": [ "MD004", "ul-style" ],
25
36
  "description": "Unordered list style",
26
37
  "tags": [ "bullet", "ul" ],
27
- "parser": "none",
38
+ "parser": "micromark",
28
39
  "function": function MD004(params, onError) {
29
40
  const style = String(params.config.style || "consistent");
30
- let expectedStyle = style;
41
+ let expectedStyle = validStyles.has(style) ? style : "dash";
31
42
  const nestingStyles = [];
32
- for (const list of flattenedLists()) {
33
- if (list.unordered) {
34
- if (expectedStyle === "consistent") {
35
- expectedStyle = unorderedListStyleFor(list.items[0]);
43
+ for (const listUnordered of filterByTypesCached([ "listUnordered" ])) {
44
+ let nesting = 0;
45
+ if (style === "sublist") {
46
+ /** @type {import("../helpers/micromark-helpers.cjs").Token | null} */
47
+ let parent = listUnordered;
48
+ // @ts-ignore
49
+ while ((parent = getParentOfType(parent, [ "listOrdered", "listUnordered" ]))) {
50
+ nesting++;
36
51
  }
37
- for (const item of list.items) {
38
- const itemStyle = unorderedListStyleFor(item);
39
- if (style === "sublist") {
40
- const nesting = list.nesting;
41
- if (!nestingStyles[nesting]) {
42
- nestingStyles[nesting] =
43
- (itemStyle === nestingStyles[nesting - 1]) ?
44
- differentItemStyle[itemStyle] :
45
- itemStyle;
46
- }
47
- expectedStyle = nestingStyles[nesting];
48
- }
49
- if (!validStyles.includes(expectedStyle)) {
50
- expectedStyle = validStyles[0];
51
- }
52
- let range = null;
53
- let fixInfo = null;
54
- const match = item.line.match(listItemMarkerRe);
55
- if (match) {
56
- const column = match.index + 1;
57
- const length = match[0].length;
58
- range = [ column, length ];
59
- fixInfo = {
60
- "editColumn": match[1].length + 1,
61
- "deleteCount": 1,
62
- "insertText": expectedStyleToMarker[expectedStyle]
63
- };
52
+ }
53
+ const listItemMarkers = getDescendantsByType(listUnordered, [ "listItemPrefix", "listItemMarker" ]);
54
+ for (const listItemMarker of listItemMarkers) {
55
+ const itemStyle = markerToStyle[listItemMarker.text];
56
+ if (style === "sublist") {
57
+ if (!nestingStyles[nesting]) {
58
+ nestingStyles[nesting] =
59
+ (itemStyle === nestingStyles[nesting - 1]) ?
60
+ differentItemStyle[itemStyle] :
61
+ itemStyle;
64
62
  }
65
- addErrorDetailIf(
66
- onError,
67
- item.lineNumber,
68
- expectedStyle,
69
- itemStyle,
70
- null,
71
- null,
72
- range,
73
- fixInfo
74
- );
63
+ expectedStyle = nestingStyles[nesting];
64
+ } else if (expectedStyle === "consistent") {
65
+ expectedStyle = itemStyle;
75
66
  }
67
+ const column = listItemMarker.startColumn;
68
+ const length = listItemMarker.endColumn - listItemMarker.startColumn;
69
+ addErrorDetailIf(
70
+ onError,
71
+ listItemMarker.startLine,
72
+ expectedStyle,
73
+ itemStyle,
74
+ undefined,
75
+ undefined,
76
+ [ column, length ],
77
+ {
78
+ "editColumn": column,
79
+ "deleteCount": length,
80
+ "insertText": styleToMarker[expectedStyle]
81
+ }
82
+ );
76
83
  }
77
84
  }
78
85
  }