markdownlint 0.25.1 → 0.26.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 (49) hide show
  1. package/CONTRIBUTING.md +2 -0
  2. package/README.md +123 -83
  3. package/demo/markdownlint-browser.js +1716 -1269
  4. package/doc/Rules.md +133 -15
  5. package/helpers/helpers.js +427 -192
  6. package/helpers/package.json +4 -1
  7. package/lib/cache.js +15 -25
  8. package/lib/constants.js +1 -1
  9. package/lib/markdownlint.d.ts +21 -17
  10. package/lib/markdownlint.js +237 -146
  11. package/lib/md004.js +4 -4
  12. package/lib/md005.js +4 -4
  13. package/lib/md006.js +4 -4
  14. package/lib/md007.js +4 -4
  15. package/lib/md009.js +20 -13
  16. package/lib/md010.js +15 -2
  17. package/lib/md011.js +2 -2
  18. package/lib/md013.js +4 -5
  19. package/lib/md014.js +4 -4
  20. package/lib/md021.js +3 -1
  21. package/lib/md026.js +1 -1
  22. package/lib/md027.js +2 -2
  23. package/lib/md028.js +2 -2
  24. package/lib/md029.js +5 -4
  25. package/lib/md030.js +4 -4
  26. package/lib/md031.js +2 -2
  27. package/lib/md032.js +3 -2
  28. package/lib/md033.js +2 -3
  29. package/lib/md034.js +2 -2
  30. package/lib/md035.js +7 -2
  31. package/lib/md036.js +2 -2
  32. package/lib/md038.js +7 -4
  33. package/lib/md039.js +6 -4
  34. package/lib/md042.js +17 -10
  35. package/lib/md043.js +2 -2
  36. package/lib/md044.js +20 -9
  37. package/lib/md046.js +14 -13
  38. package/lib/md048.js +13 -14
  39. package/lib/md049-md050.js +73 -0
  40. package/lib/md051.js +84 -0
  41. package/lib/md052.js +35 -0
  42. package/lib/md053.js +52 -0
  43. package/lib/rules.js +6 -4
  44. package/package.json +19 -16
  45. package/schema/.markdownlint.jsonc +15 -2
  46. package/schema/.markdownlint.yaml +14 -1
  47. package/schema/markdownlint-config-schema.json +119 -737
  48. package/lib/md049.js +0 -45
  49. package/lib/md050.js +0 -45
@@ -20,7 +20,7 @@ const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? requir
20
20
  *
21
21
  * @param {Rule[]} ruleList List of rules.
22
22
  * @param {boolean} synchronous Whether to execute synchronously.
23
- * @returns {string} Error message if validation fails.
23
+ * @returns {Error | null} Error message if validation fails.
24
24
  */
25
25
  function validateRuleList(ruleList, synchronous) {
26
26
  let result = null;
@@ -29,32 +29,32 @@ function validateRuleList(ruleList, synchronous) {
29
29
  return result;
30
30
  }
31
31
  const allIds = {};
32
- ruleList.forEach(function forRule(rule, index) {
32
+ for (const [ index, rule ] of ruleList.entries()) {
33
33
  const customIndex = index - rules.length;
34
- // eslint-disable-next-line jsdoc/require-jsdoc
34
+ // eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc
35
35
  function newError(property) {
36
36
  return new Error(
37
37
  "Property '" + property + "' of custom rule at index " +
38
38
  customIndex + " is incorrect.");
39
39
  }
40
- [ "names", "tags" ].forEach(function forProperty(property) {
40
+ for (const property of [ "names", "tags" ]) {
41
41
  const value = rule[property];
42
42
  if (!result &&
43
43
  (!value || !Array.isArray(value) || (value.length === 0) ||
44
44
  !value.every(helpers.isString) || value.some(helpers.isEmptyString))) {
45
45
  result = newError(property);
46
46
  }
47
- });
48
- [
47
+ }
48
+ for (const propertyInfo of [
49
49
  [ "description", "string" ],
50
50
  [ "function", "function" ]
51
- ].forEach(function forProperty(propertyInfo) {
51
+ ]) {
52
52
  const property = propertyInfo[0];
53
53
  const value = rule[property];
54
54
  if (!result && (!value || (typeof value !== propertyInfo[1]))) {
55
55
  result = newError(property);
56
56
  }
57
- });
57
+ }
58
58
  if (
59
59
  !result &&
60
60
  rule.information &&
@@ -76,24 +76,24 @@ function validateRuleList(ruleList, synchronous) {
76
76
  );
77
77
  }
78
78
  if (!result) {
79
- rule.names.forEach(function forName(name) {
79
+ for (const name of rule.names) {
80
80
  const nameUpper = name.toUpperCase();
81
81
  if (!result && (allIds[nameUpper] !== undefined)) {
82
82
  result = new Error("Name '" + name + "' of custom rule at index " +
83
83
  customIndex + " is already used as a name or tag.");
84
84
  }
85
85
  allIds[nameUpper] = true;
86
- });
87
- rule.tags.forEach(function forTag(tag) {
86
+ }
87
+ for (const tag of rule.tags) {
88
88
  const tagUpper = tag.toUpperCase();
89
89
  if (!result && allIds[tagUpper]) {
90
90
  result = new Error("Tag '" + tag + "' of custom rule at index " +
91
91
  customIndex + " is already used as a name.");
92
92
  }
93
93
  allIds[tagUpper] = false;
94
- });
94
+ }
95
95
  }
96
- });
96
+ }
97
97
  return result;
98
98
  }
99
99
 
@@ -111,10 +111,10 @@ function newResults(ruleList) {
111
111
  const results = [];
112
112
  const keys = Object.keys(lintResults);
113
113
  keys.sort();
114
- keys.forEach(function forFile(file) {
114
+ for (const file of keys) {
115
115
  const fileResults = lintResults[file];
116
116
  if (Array.isArray(fileResults)) {
117
- fileResults.forEach(function forResult(result) {
117
+ for (const result of fileResults) {
118
118
  const ruleMoniker = result.ruleNames ?
119
119
  result.ruleNames.join("/") :
120
120
  (result.ruleName + "/" + result.ruleAlias);
@@ -129,30 +129,32 @@ function newResults(ruleList) {
129
129
  (result.errorContext ?
130
130
  " [Context: \"" + result.errorContext + "\"]" :
131
131
  ""));
132
- });
132
+ }
133
133
  } else {
134
134
  if (!ruleNameToRule) {
135
135
  ruleNameToRule = {};
136
- ruleList.forEach(function forRule(rule) {
136
+ for (const rule of ruleList) {
137
137
  const ruleName = rule.names[0].toUpperCase();
138
138
  ruleNameToRule[ruleName] = rule;
139
- });
139
+ }
140
140
  }
141
- Object.keys(fileResults).forEach(function forRule(ruleName) {
141
+ for (const [ ruleName, ruleResults ] of Object.entries(fileResults)) {
142
142
  const rule = ruleNameToRule[ruleName.toUpperCase()];
143
- const ruleResults = fileResults[ruleName];
144
- ruleResults.forEach(function forLine(lineNumber) {
143
+ for (const lineNumber of ruleResults) {
144
+ // @ts-ignore
145
145
  const nameIndex = Math.min(useAlias ? 1 : 0, rule.names.length - 1);
146
146
  const result =
147
147
  file + ": " +
148
148
  lineNumber + ": " +
149
+ // @ts-ignore
149
150
  rule.names[nameIndex] + " " +
151
+ // @ts-ignore
150
152
  rule.description;
151
153
  results.push(result);
152
- });
153
- });
154
+ }
155
+ }
154
156
  }
155
- });
157
+ }
156
158
  return results.join("\n");
157
159
  }
158
160
  Object.defineProperty(lintResults, "toString", { "value": toString });
@@ -188,15 +190,40 @@ function removeFrontMatter(content, frontMatter) {
188
190
  }
189
191
 
190
192
  /**
191
- * Annotate tokens with line/lineNumber.
193
+ * Freeze all freeze-able members of a token and its children.
194
+ *
195
+ * @param {MarkdownItToken} token A markdown-it token.
196
+ * @returns {void}
197
+ */
198
+ function freezeToken(token) {
199
+ if (token.attrs) {
200
+ for (const attr of token.attrs) {
201
+ Object.freeze(attr);
202
+ }
203
+ Object.freeze(token.attrs);
204
+ }
205
+ if (token.children) {
206
+ for (const child of token.children) {
207
+ freezeToken(child);
208
+ }
209
+ Object.freeze(token.children);
210
+ }
211
+ if (token.map) {
212
+ Object.freeze(token.map);
213
+ }
214
+ Object.freeze(token);
215
+ }
216
+
217
+ /**
218
+ * Annotate tokens with line/lineNumber and freeze them.
192
219
  *
193
220
  * @param {MarkdownItToken[]} tokens Array of markdown-it tokens.
194
221
  * @param {string[]} lines Lines of Markdown content.
195
222
  * @returns {void}
196
223
  */
197
- function annotateTokens(tokens, lines) {
224
+ function annotateAndFreezeTokens(tokens, lines) {
198
225
  let trMap = null;
199
- tokens.forEach(function forToken(token) {
226
+ for (const token of tokens) {
200
227
  // Provide missing maps for table content
201
228
  if (token.type === "tr_open") {
202
229
  trMap = token.map;
@@ -219,16 +246,17 @@ function annotateTokens(tokens, lines) {
219
246
  while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
220
247
  token.map[1]--;
221
248
  }
222
- // Annotate children with lineNumber
223
- let lineNumber = token.lineNumber;
249
+ }
250
+ // Annotate children with lineNumber
251
+ if (token.children) {
224
252
  const codeSpanExtraLines = [];
225
- helpers.forEachInlineCodeSpan(
226
- token.content,
227
- function handleInlineCodeSpan(code) {
253
+ if (token.children.some((child) => child.type === "code_inline")) {
254
+ helpers.forEachInlineCodeSpan(token.content, (code) => {
228
255
  codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1);
229
- }
230
- );
231
- (token.children || []).forEach(function forChild(child) {
256
+ });
257
+ }
258
+ let lineNumber = token.lineNumber;
259
+ for (const child of token.children) {
232
260
  child.lineNumber = lineNumber;
233
261
  child.line = lines[lineNumber - 1];
234
262
  if ((child.type === "softbreak") || (child.type === "hardbreak")) {
@@ -236,9 +264,11 @@ function annotateTokens(tokens, lines) {
236
264
  } else if (child.type === "code_inline") {
237
265
  lineNumber += codeSpanExtraLines.shift();
238
266
  }
239
- });
267
+ }
240
268
  }
241
- });
269
+ freezeToken(token);
270
+ }
271
+ Object.freeze(tokens);
242
272
  }
243
273
 
244
274
  /**
@@ -250,24 +280,24 @@ function annotateTokens(tokens, lines) {
250
280
  function mapAliasToRuleNames(ruleList) {
251
281
  const aliasToRuleNames = {};
252
282
  // const tagToRuleNames = {};
253
- ruleList.forEach(function forRule(rule) {
283
+ for (const rule of ruleList) {
254
284
  const ruleName = rule.names[0].toUpperCase();
255
285
  // The following is useful for updating README.md:
256
286
  // console.log(
257
287
  // "* **[" + ruleName + "](doc/Rules.md#" + ruleName.toLowerCase() +
258
288
  // ")** *" + rule.names.slice(1).join("/") + "* - " + rule.description);
259
- rule.names.forEach(function forName(name) {
289
+ for (const name of rule.names) {
260
290
  const nameUpper = name.toUpperCase();
261
291
  aliasToRuleNames[nameUpper] = [ ruleName ];
262
- });
263
- rule.tags.forEach(function forTag(tag) {
292
+ }
293
+ for (const tag of rule.tags) {
264
294
  const tagUpper = tag.toUpperCase();
265
295
  const ruleNames = aliasToRuleNames[tagUpper] || [];
266
296
  ruleNames.push(ruleName);
267
297
  aliasToRuleNames[tagUpper] = ruleNames;
268
298
  // tagToRuleNames[tag] = ruleName;
269
- });
270
- });
299
+ }
300
+ }
271
301
  // The following is useful for updating README.md:
272
302
  // Object.keys(tagToRuleNames).sort().forEach(function forTag(tag) {
273
303
  // console.log("* **" + tag + "** - " +
@@ -292,14 +322,14 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
292
322
  );
293
323
  const ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]];
294
324
  const effectiveConfig = {};
295
- ruleList.forEach((rule) => {
325
+ for (const rule of ruleList) {
296
326
  const ruleName = rule.names[0].toUpperCase();
297
327
  effectiveConfig[ruleName] = ruleDefault;
298
- });
299
- deprecatedRuleNames.forEach((ruleName) => {
328
+ }
329
+ for (const ruleName of deprecatedRuleNames) {
300
330
  effectiveConfig[ruleName] = false;
301
- });
302
- Object.keys(config).forEach((key) => {
331
+ }
332
+ for (const key of Object.keys(config)) {
303
333
  let value = config[key];
304
334
  if (value) {
305
335
  if (!(value instanceof Object)) {
@@ -309,13 +339,46 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
309
339
  value = false;
310
340
  }
311
341
  const keyUpper = key.toUpperCase();
312
- (aliasToRuleNames[keyUpper] || []).forEach((ruleName) => {
342
+ for (const ruleName of (aliasToRuleNames[keyUpper] || [])) {
313
343
  effectiveConfig[ruleName] = value;
314
- });
315
- });
344
+ }
345
+ }
316
346
  return effectiveConfig;
317
347
  }
318
348
 
349
+ /**
350
+ * Parse the content of a configuration file.
351
+ *
352
+ * @param {string} name Name of the configuration file.
353
+ * @param {string} content Configuration content.
354
+ * @param {ConfigurationParser[] | null} [parsers] Parsing function(s).
355
+ * @returns {Object} Configuration object and error message.
356
+ */
357
+ function parseConfiguration(name, content, parsers) {
358
+ let config = null;
359
+ let message = "";
360
+ const errors = [];
361
+ let index = 0;
362
+ // Try each parser
363
+ (parsers || [ JSON.parse ]).every((parser) => {
364
+ try {
365
+ config = parser(content);
366
+ } catch (error) {
367
+ errors.push(`Parser ${index++}: ${error.message}`);
368
+ }
369
+ return !config;
370
+ });
371
+ // Message if unable to parse
372
+ if (!config) {
373
+ errors.unshift(`Unable to parse '${name}'`);
374
+ message = errors.join("; ");
375
+ }
376
+ return {
377
+ config,
378
+ message
379
+ };
380
+ }
381
+
319
382
  /**
320
383
  * Create a mapping of enabled rules per line.
321
384
  *
@@ -324,6 +387,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
324
387
  * @param {string[]} frontMatterLines List of front matter lines.
325
388
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
326
389
  * @param {Configuration} config Configuration object.
390
+ * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
327
391
  * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
328
392
  * names.
329
393
  * @returns {Object} Effective configuration and enabled rules per line number.
@@ -334,6 +398,7 @@ function getEnabledRulesPerLineNumber(
334
398
  frontMatterLines,
335
399
  noInlineConfig,
336
400
  config,
401
+ configParsers,
337
402
  aliasToRuleNames) {
338
403
  // Shared variables
339
404
  let enabledRules = {};
@@ -343,31 +408,36 @@ function getEnabledRulesPerLineNumber(
343
408
  // Helper functions
344
409
  // eslint-disable-next-line jsdoc/require-jsdoc
345
410
  function handleInlineConfig(input, forEachMatch, forEachLine) {
346
- input.forEach((line, lineIndex) => {
411
+ for (const [ lineIndex, line ] of input.entries()) {
347
412
  if (!noInlineConfig) {
348
413
  let match = null;
349
- while ((match = helpers.inlineCommentRe.exec(line))) {
350
- const action = (match[1] || match[3]).toUpperCase();
351
- const parameter = match[2] || match[4];
414
+ while ((match = helpers.inlineCommentStartRe.exec(line))) {
415
+ const action = match[2].toUpperCase();
416
+ const startIndex = match.index + match[1].length;
417
+ const endIndex = line.indexOf("-->", startIndex);
418
+ if (endIndex === -1) {
419
+ break;
420
+ }
421
+ const parameter = line.slice(startIndex, endIndex);
352
422
  forEachMatch(action, parameter, lineIndex + 1);
353
423
  }
354
424
  }
355
425
  if (forEachLine) {
356
426
  forEachLine();
357
427
  }
358
- });
428
+ }
359
429
  }
360
430
  // eslint-disable-next-line jsdoc/require-jsdoc
361
431
  function configureFile(action, parameter) {
362
432
  if (action === "CONFIGURE-FILE") {
363
- try {
364
- const json = JSON.parse(parameter);
433
+ const { "config": parsed } = parseConfiguration(
434
+ "CONFIGURE-FILE", parameter, configParsers
435
+ );
436
+ if (parsed) {
365
437
  config = {
366
438
  ...config,
367
- ...json
439
+ ...parsed
368
440
  };
369
- } catch {
370
- // Ignore parse errors for inline configuration
371
441
  }
372
442
  }
373
443
  }
@@ -375,14 +445,13 @@ function getEnabledRulesPerLineNumber(
375
445
  function applyEnableDisable(action, parameter, state) {
376
446
  state = { ...state };
377
447
  const enabled = (action.startsWith("ENABLE"));
378
- const items = parameter ?
379
- parameter.trim().toUpperCase().split(/\s+/) :
380
- allRuleNames;
381
- items.forEach((nameUpper) => {
382
- (aliasToRuleNames[nameUpper] || []).forEach((ruleName) => {
448
+ const trimmed = parameter && parameter.trim();
449
+ const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames;
450
+ for (const nameUpper of items) {
451
+ for (const ruleName of (aliasToRuleNames[nameUpper] || [])) {
383
452
  state[ruleName] = enabled;
384
- });
385
- });
453
+ }
454
+ }
386
455
  return state;
387
456
  }
388
457
  // eslint-disable-next-line jsdoc/require-jsdoc
@@ -406,9 +475,12 @@ function getEnabledRulesPerLineNumber(
406
475
  enabledRulesPerLineNumber.push(enabledRules);
407
476
  }
408
477
  // eslint-disable-next-line jsdoc/require-jsdoc
409
- function disableNextLine(action, parameter, lineNumber) {
410
- if (action === "DISABLE-NEXT-LINE") {
411
- const nextLineNumber = frontMatterLines.length + lineNumber + 1;
478
+ function disableLineNextLine(action, parameter, lineNumber) {
479
+ const disableLine = (action === "DISABLE-LINE");
480
+ const disableNextLine = (action === "DISABLE-NEXT-LINE");
481
+ if (disableLine || disableNextLine) {
482
+ const nextLineNumber =
483
+ frontMatterLines.length + lineNumber + (disableNextLine ? 1 : 0);
412
484
  enabledRulesPerLineNumber[nextLineNumber] =
413
485
  applyEnableDisable(
414
486
  action,
@@ -421,15 +493,15 @@ function getEnabledRulesPerLineNumber(
421
493
  handleInlineConfig([ lines.join("\n") ], configureFile);
422
494
  const effectiveConfig = getEffectiveConfig(
423
495
  ruleList, config, aliasToRuleNames);
424
- ruleList.forEach((rule) => {
496
+ for (const rule of ruleList) {
425
497
  const ruleName = rule.names[0].toUpperCase();
426
498
  allRuleNames.push(ruleName);
427
499
  enabledRules[ruleName] = !!effectiveConfig[ruleName];
428
- });
500
+ }
429
501
  capturedRules = enabledRules;
430
502
  handleInlineConfig(lines, enableDisableFile);
431
503
  handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
432
- handleInlineConfig(lines, disableNextLine);
504
+ handleInlineConfig(lines, disableLineNextLine);
433
505
  // Return results
434
506
  return {
435
507
  effectiveConfig,
@@ -445,6 +517,7 @@ function getEnabledRulesPerLineNumber(
445
517
  * @param {string} content Markdown content.
446
518
  * @param {Object} md Instance of markdown-it.
447
519
  * @param {Configuration} config Configuration object.
520
+ * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
448
521
  * @param {RegExp} frontMatter Regular expression for front matter.
449
522
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
450
523
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
@@ -458,6 +531,7 @@ function lintContent(
458
531
  content,
459
532
  md,
460
533
  config,
534
+ configParsers,
461
535
  frontMatter,
462
536
  handleRuleFailures,
463
537
  noInlineConfig,
@@ -467,42 +541,59 @@ function lintContent(
467
541
  content = content.replace(/^\uFEFF/, "");
468
542
  // Remove front matter
469
543
  const removeFrontMatterResult = removeFrontMatter(content, frontMatter);
470
- const frontMatterLines = removeFrontMatterResult.frontMatterLines;
471
- // Ignore the content of HTML comments
472
- content = helpers.clearHtmlCommentText(removeFrontMatterResult.content);
473
- // Parse content into tokens and lines
474
- const tokens = md.parse(content, {});
475
- const lines = content.split(helpers.newLineRe);
476
- annotateTokens(tokens, lines);
477
- const aliasToRuleNames = mapAliasToRuleNames(ruleList);
544
+ const { frontMatterLines } = removeFrontMatterResult;
545
+ content = removeFrontMatterResult.content;
546
+ // Get enabled rules per line (with HTML comments present)
478
547
  const { effectiveConfig, enabledRulesPerLineNumber } =
479
548
  getEnabledRulesPerLineNumber(
480
549
  ruleList,
481
- lines,
550
+ content.split(helpers.newLineRe),
482
551
  frontMatterLines,
483
552
  noInlineConfig,
484
553
  config,
485
- aliasToRuleNames
554
+ configParsers,
555
+ mapAliasToRuleNames(ruleList)
486
556
  );
487
- // Create parameters for rules
488
- const params = {
489
- "name": helpers.deepFreeze(name),
490
- "tokens": helpers.deepFreeze(tokens),
491
- "lines": helpers.deepFreeze(lines),
492
- "frontMatterLines": helpers.deepFreeze(frontMatterLines)
557
+ // Hide the content of HTML comments from rules, etc.
558
+ content = helpers.clearHtmlCommentText(content);
559
+ // Parse content into tokens and lines
560
+ const tokens = md.parse(content, {});
561
+ const lines = content.split(helpers.newLineRe);
562
+ annotateAndFreezeTokens(tokens, lines);
563
+ // Create (frozen) parameters for rules
564
+ const paramsBase = {
565
+ name,
566
+ tokens,
567
+ "lines": Object.freeze(lines),
568
+ "frontMatterLines": Object.freeze(frontMatterLines)
493
569
  };
494
- cache.lineMetadata(helpers.getLineMetadata(params));
495
- cache.flattenedLists(helpers.flattenLists(params.tokens));
496
- cache.codeBlockAndSpanRanges(
497
- helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())
498
- );
570
+ const lineMetadata =
571
+ helpers.getLineMetadata(paramsBase);
572
+ const codeBlockAndSpanRanges =
573
+ helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata);
574
+ const flattenedLists =
575
+ helpers.flattenLists(paramsBase.tokens);
576
+ const htmlElementRanges =
577
+ helpers.htmlElementRanges(paramsBase, lineMetadata);
578
+ const referenceLinkImageData =
579
+ helpers.getReferenceLinkImageData(lineMetadata);
580
+ cache.set({
581
+ codeBlockAndSpanRanges,
582
+ flattenedLists,
583
+ htmlElementRanges,
584
+ lineMetadata,
585
+ referenceLinkImageData
586
+ });
499
587
  // Function to run for each rule
500
588
  let results = [];
501
589
  // eslint-disable-next-line jsdoc/require-jsdoc
502
590
  function forRule(rule) {
503
591
  // Configure rule
504
592
  const ruleName = rule.names[0].toUpperCase();
505
- params.config = effectiveConfig[ruleName];
593
+ const params = {
594
+ ...paramsBase,
595
+ "config": effectiveConfig[ruleName]
596
+ };
506
597
  // eslint-disable-next-line jsdoc/require-jsdoc
507
598
  function throwError(property) {
508
599
  throw new Error(
@@ -697,6 +788,7 @@ function lintContent(
697
788
  * @param {string} file Path of file to lint.
698
789
  * @param {Object} md Instance of markdown-it.
699
790
  * @param {Configuration} config Configuration object.
791
+ * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
700
792
  * @param {RegExp} frontMatter Regular expression for front matter.
701
793
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
702
794
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
@@ -711,6 +803,7 @@ function lintFile(
711
803
  file,
712
804
  md,
713
805
  config,
806
+ configParsers,
714
807
  frontMatter,
715
808
  handleRuleFailures,
716
809
  noInlineConfig,
@@ -723,8 +816,19 @@ function lintFile(
723
816
  if (err) {
724
817
  return callback(err);
725
818
  }
726
- return lintContent(ruleList, file, content, md, config, frontMatter,
727
- handleRuleFailures, noInlineConfig, resultVersion, callback);
819
+ return lintContent(
820
+ ruleList,
821
+ file,
822
+ content,
823
+ md,
824
+ config,
825
+ configParsers,
826
+ frontMatter,
827
+ handleRuleFailures,
828
+ noInlineConfig,
829
+ resultVersion,
830
+ callback
831
+ );
728
832
  }
729
833
  // Make a/synchronous call to read file
730
834
  if (synchronous) {
@@ -750,7 +854,8 @@ function lintInput(options, synchronous, callback) {
750
854
  const ruleList = rules.concat(options.customRules || []);
751
855
  const ruleErr = validateRuleList(ruleList, synchronous);
752
856
  if (ruleErr) {
753
- return callback(ruleErr);
857
+ callback(ruleErr);
858
+ return;
754
859
  }
755
860
  let files = [];
756
861
  if (Array.isArray(options.files)) {
@@ -761,18 +866,19 @@ function lintInput(options, synchronous, callback) {
761
866
  const strings = options.strings || {};
762
867
  const stringsKeys = Object.keys(strings);
763
868
  const config = options.config || { "default": true };
869
+ const configParsers = options.configParsers || null;
764
870
  const frontMatter = (options.frontMatter === undefined) ?
765
871
  helpers.frontMatterRe : options.frontMatter;
766
872
  const handleRuleFailures = !!options.handleRuleFailures;
767
873
  const noInlineConfig = !!options.noInlineConfig;
768
874
  const resultVersion = (options.resultVersion === undefined) ?
769
- 2 : options.resultVersion;
875
+ 3 : options.resultVersion;
770
876
  const md = markdownIt({ "html": true });
771
877
  const markdownItPlugins = options.markdownItPlugins || [];
772
- markdownItPlugins.forEach(function forPlugin(plugin) {
878
+ for (const plugin of markdownItPlugins) {
773
879
  // @ts-ignore
774
880
  md.use(...plugin);
775
- });
881
+ }
776
882
  const fs = options.fs || require("fs");
777
883
  const results = newResults(ruleList);
778
884
  let done = false;
@@ -804,6 +910,7 @@ function lintInput(options, synchronous, callback) {
804
910
  currentItem,
805
911
  md,
806
912
  config,
913
+ configParsers,
807
914
  frontMatter,
808
915
  handleRuleFailures,
809
916
  noInlineConfig,
@@ -812,16 +919,16 @@ function lintInput(options, synchronous, callback) {
812
919
  synchronous,
813
920
  lintWorkerCallback
814
921
  );
815
- } else if (stringsKeys.length > 0) {
922
+ } else if ((currentItem = stringsKeys.shift())) {
816
923
  // Lint next string
817
924
  concurrency++;
818
- currentItem = stringsKeys.shift();
819
925
  lintContent(
820
926
  ruleList,
821
927
  currentItem,
822
928
  strings[currentItem] || "",
823
929
  md,
824
930
  config,
931
+ configParsers,
825
932
  frontMatter,
826
933
  handleRuleFailures,
827
934
  noInlineConfig,
@@ -852,7 +959,6 @@ function lintInput(options, synchronous, callback) {
852
959
  lintWorker();
853
960
  lintWorker();
854
961
  }
855
- return null;
856
962
  }
857
963
 
858
964
  /**
@@ -875,6 +981,7 @@ const markdownlintPromisify = promisify && promisify(markdownlint);
875
981
  * @returns {Promise<LintResults>} Results object.
876
982
  */
877
983
  function markdownlintPromise(options) {
984
+ // @ts-ignore
878
985
  return markdownlintPromisify(options);
879
986
  }
880
987
 
@@ -885,49 +992,17 @@ function markdownlintPromise(options) {
885
992
  * @returns {LintResults} Results object.
886
993
  */
887
994
  function markdownlintSync(options) {
888
- let results = null;
995
+ let results = {};
889
996
  lintInput(options, true, function callback(error, res) {
890
997
  if (error) {
891
998
  throw error;
892
999
  }
893
1000
  results = res;
894
1001
  });
1002
+ // @ts-ignore
895
1003
  return results;
896
1004
  }
897
1005
 
898
- /**
899
- * Parse the content of a configuration file.
900
- *
901
- * @param {string} name Name of the configuration file.
902
- * @param {string} content Configuration content.
903
- * @param {ConfigurationParser[]} parsers Parsing function(s).
904
- * @returns {Object} Configuration object and error message.
905
- */
906
- function parseConfiguration(name, content, parsers) {
907
- let config = null;
908
- let message = "";
909
- const errors = [];
910
- let index = 0;
911
- // Try each parser
912
- (parsers || [ JSON.parse ]).every((parser) => {
913
- try {
914
- config = parser(content);
915
- } catch (error) {
916
- errors.push(`Parser ${index++}: ${error.message}`);
917
- }
918
- return !config;
919
- });
920
- // Message if unable to parse
921
- if (!config) {
922
- errors.unshift(`Unable to parse '${name}'`);
923
- message = errors.join("; ");
924
- }
925
- return {
926
- config,
927
- message
928
- };
929
- }
930
-
931
1006
  /**
932
1007
  * Resolve referenced "extends" path in a configuration file
933
1008
  * using path.resolve() with require.resolve() as a fallback.
@@ -935,7 +1010,7 @@ function parseConfiguration(name, content, parsers) {
935
1010
  * @param {string} configFile Configuration file name.
936
1011
  * @param {string} referenceId Referenced identifier to resolve.
937
1012
  * @param {Object} fs File system implementation.
938
- * @param {ResolveConfigExtendsCallback} [callback] Callback (err, result)
1013
+ * @param {ResolveConfigExtendsCallback} callback Callback (err, result)
939
1014
  * function.
940
1015
  * @returns {void}
941
1016
  */
@@ -1005,6 +1080,7 @@ function readConfig(file, parsers, fs, callback) {
1005
1080
  } else {
1006
1081
  // @ts-ignore
1007
1082
  callback = parsers;
1083
+ // @ts-ignore
1008
1084
  parsers = null;
1009
1085
  }
1010
1086
  }
@@ -1012,14 +1088,18 @@ function readConfig(file, parsers, fs, callback) {
1012
1088
  fs = require("fs");
1013
1089
  }
1014
1090
  // Read file
1091
+ const os = require("os");
1092
+ file = helpers.expandTildePath(file, os);
1015
1093
  fs.readFile(file, "utf8", (err, content) => {
1016
1094
  if (err) {
1095
+ // @ts-ignore
1017
1096
  return callback(err);
1018
1097
  }
1019
1098
  // Try to parse file
1020
1099
  // @ts-ignore
1021
1100
  const { config, message } = parseConfiguration(file, content, parsers);
1022
1101
  if (!config) {
1102
+ // @ts-ignore
1023
1103
  return callback(new Error(message));
1024
1104
  }
1025
1105
  // Extend configuration
@@ -1028,16 +1108,19 @@ function readConfig(file, parsers, fs, callback) {
1028
1108
  delete config.extends;
1029
1109
  return resolveConfigExtends(
1030
1110
  file,
1031
- configExtends,
1111
+ helpers.expandTildePath(configExtends, os),
1032
1112
  fs,
1033
1113
  (_, resolvedExtends) => readConfig(
1114
+ // @ts-ignore
1034
1115
  resolvedExtends,
1035
1116
  parsers,
1036
1117
  fs,
1037
1118
  (errr, extendsConfig) => {
1038
1119
  if (errr) {
1120
+ // @ts-ignore
1039
1121
  return callback(errr);
1040
1122
  }
1123
+ // @ts-ignore
1041
1124
  return callback(null, {
1042
1125
  ...extendsConfig,
1043
1126
  ...config
@@ -1046,6 +1129,7 @@ function readConfig(file, parsers, fs, callback) {
1046
1129
  )
1047
1130
  );
1048
1131
  }
1132
+ // @ts-ignore
1049
1133
  return callback(null, config);
1050
1134
  });
1051
1135
  }
@@ -1079,6 +1163,8 @@ function readConfigSync(file, parsers, fs) {
1079
1163
  fs = require("fs");
1080
1164
  }
1081
1165
  // Read file
1166
+ const os = require("os");
1167
+ file = helpers.expandTildePath(file, os);
1082
1168
  const content = fs.readFileSync(file, "utf8");
1083
1169
  // Try to parse file
1084
1170
  const { config, message } = parseConfiguration(file, content, parsers);
@@ -1089,7 +1175,11 @@ function readConfigSync(file, parsers, fs) {
1089
1175
  const configExtends = config.extends;
1090
1176
  if (configExtends) {
1091
1177
  delete config.extends;
1092
- const resolvedExtends = resolveConfigExtendsSync(file, configExtends, fs);
1178
+ const resolvedExtends = resolveConfigExtendsSync(
1179
+ file,
1180
+ helpers.expandTildePath(configExtends, os),
1181
+ fs
1182
+ );
1093
1183
  return {
1094
1184
  ...readConfigSync(resolvedExtends, parsers, fs),
1095
1185
  ...config
@@ -1206,16 +1296,17 @@ module.exports = markdownlint;
1206
1296
  * Configuration options.
1207
1297
  *
1208
1298
  * @typedef {Object} Options
1209
- * @property {string[] | string} [files] Files to lint.
1210
- * @property {Object.<string, string>} [strings] Strings to lint.
1211
1299
  * @property {Configuration} [config] Configuration object.
1300
+ * @property {ConfigurationParser[]} [configParsers] Configuration parsers.
1212
1301
  * @property {Rule[] | Rule} [customRules] Custom rules.
1302
+ * @property {string[] | string} [files] Files to lint.
1213
1303
  * @property {RegExp} [frontMatter] Front matter pattern.
1304
+ * @property {Object} [fs] File system implementation.
1214
1305
  * @property {boolean} [handleRuleFailures] True to catch exceptions.
1306
+ * @property {Plugin[]} [markdownItPlugins] Additional plugins.
1215
1307
  * @property {boolean} [noInlineConfig] True to ignore HTML directives.
1216
1308
  * @property {number} [resultVersion] Results object version.
1217
- * @property {Plugin[]} [markdownItPlugins] Additional plugins.
1218
- * @property {Object} [fs] File system implementation.
1309
+ * @property {Object.<string, string>} [strings] Strings to lint.
1219
1310
  */
1220
1311
 
1221
1312
  /**