markdownlint 0.25.1 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CONTRIBUTING.md +2 -0
  2. package/README.md +126 -83
  3. package/demo/markdownlint-browser.js +1743 -1288
  4. package/doc/Rules.md +145 -16
  5. package/helpers/helpers.js +429 -192
  6. package/helpers/package.json +6 -2
  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 -151
  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 +7 -7
  28. package/lib/md033.js +3 -4
  29. package/lib/md034.js +2 -2
  30. package/lib/md035.js +7 -2
  31. package/lib/md036.js +2 -2
  32. package/lib/md037.js +23 -20
  33. package/lib/md038.js +7 -4
  34. package/lib/md039.js +6 -4
  35. package/lib/md042.js +17 -10
  36. package/lib/md043.js +2 -2
  37. package/lib/md044.js +20 -9
  38. package/lib/md046.js +14 -13
  39. package/lib/md048.js +13 -14
  40. package/lib/md049-md050.js +73 -0
  41. package/lib/md051.js +87 -0
  42. package/lib/md052.js +35 -0
  43. package/lib/md053.js +59 -0
  44. package/lib/rules.js +6 -4
  45. package/package.json +25 -18
  46. package/schema/.markdownlint.jsonc +19 -1
  47. package/schema/.markdownlint.yaml +18 -1
  48. package/schema/markdownlint-config-schema.json +126 -728
  49. package/lib/md049.js +0 -45
  50. 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;
@@ -206,11 +233,6 @@ function annotateTokens(tokens, lines) {
206
233
  if (!token.map && trMap) {
207
234
  token.map = [ ...trMap ];
208
235
  }
209
- // Adjust maps for math blocks
210
- if (helpers.isMathBlock(token) && token.map[1]) {
211
- // markdown-it-texmath plugin does not account for math_block_end
212
- token.map[1]++;
213
- }
214
236
  // Update token metadata
215
237
  if (token.map) {
216
238
  token.line = lines[token.map[0]];
@@ -219,16 +241,17 @@ function annotateTokens(tokens, lines) {
219
241
  while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
220
242
  token.map[1]--;
221
243
  }
222
- // Annotate children with lineNumber
223
- let lineNumber = token.lineNumber;
244
+ }
245
+ // Annotate children with lineNumber
246
+ if (token.children) {
224
247
  const codeSpanExtraLines = [];
225
- helpers.forEachInlineCodeSpan(
226
- token.content,
227
- function handleInlineCodeSpan(code) {
248
+ if (token.children.some((child) => child.type === "code_inline")) {
249
+ helpers.forEachInlineCodeSpan(token.content, (code) => {
228
250
  codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1);
229
- }
230
- );
231
- (token.children || []).forEach(function forChild(child) {
251
+ });
252
+ }
253
+ let lineNumber = token.lineNumber;
254
+ for (const child of token.children) {
232
255
  child.lineNumber = lineNumber;
233
256
  child.line = lines[lineNumber - 1];
234
257
  if ((child.type === "softbreak") || (child.type === "hardbreak")) {
@@ -236,9 +259,11 @@ function annotateTokens(tokens, lines) {
236
259
  } else if (child.type === "code_inline") {
237
260
  lineNumber += codeSpanExtraLines.shift();
238
261
  }
239
- });
262
+ }
240
263
  }
241
- });
264
+ freezeToken(token);
265
+ }
266
+ Object.freeze(tokens);
242
267
  }
243
268
 
244
269
  /**
@@ -250,24 +275,24 @@ function annotateTokens(tokens, lines) {
250
275
  function mapAliasToRuleNames(ruleList) {
251
276
  const aliasToRuleNames = {};
252
277
  // const tagToRuleNames = {};
253
- ruleList.forEach(function forRule(rule) {
278
+ for (const rule of ruleList) {
254
279
  const ruleName = rule.names[0].toUpperCase();
255
280
  // The following is useful for updating README.md:
256
281
  // console.log(
257
282
  // "* **[" + ruleName + "](doc/Rules.md#" + ruleName.toLowerCase() +
258
283
  // ")** *" + rule.names.slice(1).join("/") + "* - " + rule.description);
259
- rule.names.forEach(function forName(name) {
284
+ for (const name of rule.names) {
260
285
  const nameUpper = name.toUpperCase();
261
286
  aliasToRuleNames[nameUpper] = [ ruleName ];
262
- });
263
- rule.tags.forEach(function forTag(tag) {
287
+ }
288
+ for (const tag of rule.tags) {
264
289
  const tagUpper = tag.toUpperCase();
265
290
  const ruleNames = aliasToRuleNames[tagUpper] || [];
266
291
  ruleNames.push(ruleName);
267
292
  aliasToRuleNames[tagUpper] = ruleNames;
268
293
  // tagToRuleNames[tag] = ruleName;
269
- });
270
- });
294
+ }
295
+ }
271
296
  // The following is useful for updating README.md:
272
297
  // Object.keys(tagToRuleNames).sort().forEach(function forTag(tag) {
273
298
  // console.log("* **" + tag + "** - " +
@@ -292,14 +317,14 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
292
317
  );
293
318
  const ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]];
294
319
  const effectiveConfig = {};
295
- ruleList.forEach((rule) => {
320
+ for (const rule of ruleList) {
296
321
  const ruleName = rule.names[0].toUpperCase();
297
322
  effectiveConfig[ruleName] = ruleDefault;
298
- });
299
- deprecatedRuleNames.forEach((ruleName) => {
323
+ }
324
+ for (const ruleName of deprecatedRuleNames) {
300
325
  effectiveConfig[ruleName] = false;
301
- });
302
- Object.keys(config).forEach((key) => {
326
+ }
327
+ for (const key of Object.keys(config)) {
303
328
  let value = config[key];
304
329
  if (value) {
305
330
  if (!(value instanceof Object)) {
@@ -309,13 +334,46 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
309
334
  value = false;
310
335
  }
311
336
  const keyUpper = key.toUpperCase();
312
- (aliasToRuleNames[keyUpper] || []).forEach((ruleName) => {
337
+ for (const ruleName of (aliasToRuleNames[keyUpper] || [])) {
313
338
  effectiveConfig[ruleName] = value;
314
- });
315
- });
339
+ }
340
+ }
316
341
  return effectiveConfig;
317
342
  }
318
343
 
344
+ /**
345
+ * Parse the content of a configuration file.
346
+ *
347
+ * @param {string} name Name of the configuration file.
348
+ * @param {string} content Configuration content.
349
+ * @param {ConfigurationParser[] | null} [parsers] Parsing function(s).
350
+ * @returns {Object} Configuration object and error message.
351
+ */
352
+ function parseConfiguration(name, content, parsers) {
353
+ let config = null;
354
+ let message = "";
355
+ const errors = [];
356
+ let index = 0;
357
+ // Try each parser
358
+ (parsers || [ JSON.parse ]).every((parser) => {
359
+ try {
360
+ config = parser(content);
361
+ } catch (error) {
362
+ errors.push(`Parser ${index++}: ${error.message}`);
363
+ }
364
+ return !config;
365
+ });
366
+ // Message if unable to parse
367
+ if (!config) {
368
+ errors.unshift(`Unable to parse '${name}'`);
369
+ message = errors.join("; ");
370
+ }
371
+ return {
372
+ config,
373
+ message
374
+ };
375
+ }
376
+
319
377
  /**
320
378
  * Create a mapping of enabled rules per line.
321
379
  *
@@ -324,6 +382,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
324
382
  * @param {string[]} frontMatterLines List of front matter lines.
325
383
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
326
384
  * @param {Configuration} config Configuration object.
385
+ * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
327
386
  * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
328
387
  * names.
329
388
  * @returns {Object} Effective configuration and enabled rules per line number.
@@ -334,6 +393,7 @@ function getEnabledRulesPerLineNumber(
334
393
  frontMatterLines,
335
394
  noInlineConfig,
336
395
  config,
396
+ configParsers,
337
397
  aliasToRuleNames) {
338
398
  // Shared variables
339
399
  let enabledRules = {};
@@ -343,31 +403,36 @@ function getEnabledRulesPerLineNumber(
343
403
  // Helper functions
344
404
  // eslint-disable-next-line jsdoc/require-jsdoc
345
405
  function handleInlineConfig(input, forEachMatch, forEachLine) {
346
- input.forEach((line, lineIndex) => {
406
+ for (const [ lineIndex, line ] of input.entries()) {
347
407
  if (!noInlineConfig) {
348
408
  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];
409
+ while ((match = helpers.inlineCommentStartRe.exec(line))) {
410
+ const action = match[2].toUpperCase();
411
+ const startIndex = match.index + match[1].length;
412
+ const endIndex = line.indexOf("-->", startIndex);
413
+ if (endIndex === -1) {
414
+ break;
415
+ }
416
+ const parameter = line.slice(startIndex, endIndex);
352
417
  forEachMatch(action, parameter, lineIndex + 1);
353
418
  }
354
419
  }
355
420
  if (forEachLine) {
356
421
  forEachLine();
357
422
  }
358
- });
423
+ }
359
424
  }
360
425
  // eslint-disable-next-line jsdoc/require-jsdoc
361
426
  function configureFile(action, parameter) {
362
427
  if (action === "CONFIGURE-FILE") {
363
- try {
364
- const json = JSON.parse(parameter);
428
+ const { "config": parsed } = parseConfiguration(
429
+ "CONFIGURE-FILE", parameter, configParsers
430
+ );
431
+ if (parsed) {
365
432
  config = {
366
433
  ...config,
367
- ...json
434
+ ...parsed
368
435
  };
369
- } catch {
370
- // Ignore parse errors for inline configuration
371
436
  }
372
437
  }
373
438
  }
@@ -375,14 +440,13 @@ function getEnabledRulesPerLineNumber(
375
440
  function applyEnableDisable(action, parameter, state) {
376
441
  state = { ...state };
377
442
  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) => {
443
+ const trimmed = parameter && parameter.trim();
444
+ const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames;
445
+ for (const nameUpper of items) {
446
+ for (const ruleName of (aliasToRuleNames[nameUpper] || [])) {
383
447
  state[ruleName] = enabled;
384
- });
385
- });
448
+ }
449
+ }
386
450
  return state;
387
451
  }
388
452
  // eslint-disable-next-line jsdoc/require-jsdoc
@@ -406,9 +470,12 @@ function getEnabledRulesPerLineNumber(
406
470
  enabledRulesPerLineNumber.push(enabledRules);
407
471
  }
408
472
  // 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;
473
+ function disableLineNextLine(action, parameter, lineNumber) {
474
+ const disableLine = (action === "DISABLE-LINE");
475
+ const disableNextLine = (action === "DISABLE-NEXT-LINE");
476
+ if (disableLine || disableNextLine) {
477
+ const nextLineNumber =
478
+ frontMatterLines.length + lineNumber + (disableNextLine ? 1 : 0);
412
479
  enabledRulesPerLineNumber[nextLineNumber] =
413
480
  applyEnableDisable(
414
481
  action,
@@ -421,15 +488,15 @@ function getEnabledRulesPerLineNumber(
421
488
  handleInlineConfig([ lines.join("\n") ], configureFile);
422
489
  const effectiveConfig = getEffectiveConfig(
423
490
  ruleList, config, aliasToRuleNames);
424
- ruleList.forEach((rule) => {
491
+ for (const rule of ruleList) {
425
492
  const ruleName = rule.names[0].toUpperCase();
426
493
  allRuleNames.push(ruleName);
427
494
  enabledRules[ruleName] = !!effectiveConfig[ruleName];
428
- });
495
+ }
429
496
  capturedRules = enabledRules;
430
497
  handleInlineConfig(lines, enableDisableFile);
431
498
  handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
432
- handleInlineConfig(lines, disableNextLine);
499
+ handleInlineConfig(lines, disableLineNextLine);
433
500
  // Return results
434
501
  return {
435
502
  effectiveConfig,
@@ -445,6 +512,7 @@ function getEnabledRulesPerLineNumber(
445
512
  * @param {string} content Markdown content.
446
513
  * @param {Object} md Instance of markdown-it.
447
514
  * @param {Configuration} config Configuration object.
515
+ * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
448
516
  * @param {RegExp} frontMatter Regular expression for front matter.
449
517
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
450
518
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
@@ -458,6 +526,7 @@ function lintContent(
458
526
  content,
459
527
  md,
460
528
  config,
529
+ configParsers,
461
530
  frontMatter,
462
531
  handleRuleFailures,
463
532
  noInlineConfig,
@@ -467,42 +536,59 @@ function lintContent(
467
536
  content = content.replace(/^\uFEFF/, "");
468
537
  // Remove front matter
469
538
  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);
539
+ const { frontMatterLines } = removeFrontMatterResult;
540
+ content = removeFrontMatterResult.content;
541
+ // Get enabled rules per line (with HTML comments present)
478
542
  const { effectiveConfig, enabledRulesPerLineNumber } =
479
543
  getEnabledRulesPerLineNumber(
480
544
  ruleList,
481
- lines,
545
+ content.split(helpers.newLineRe),
482
546
  frontMatterLines,
483
547
  noInlineConfig,
484
548
  config,
485
- aliasToRuleNames
549
+ configParsers,
550
+ mapAliasToRuleNames(ruleList)
486
551
  );
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)
552
+ // Hide the content of HTML comments from rules, etc.
553
+ content = helpers.clearHtmlCommentText(content);
554
+ // Parse content into tokens and lines
555
+ const tokens = md.parse(content, {});
556
+ const lines = content.split(helpers.newLineRe);
557
+ annotateAndFreezeTokens(tokens, lines);
558
+ // Create (frozen) parameters for rules
559
+ const paramsBase = {
560
+ name,
561
+ tokens,
562
+ "lines": Object.freeze(lines),
563
+ "frontMatterLines": Object.freeze(frontMatterLines)
493
564
  };
494
- cache.lineMetadata(helpers.getLineMetadata(params));
495
- cache.flattenedLists(helpers.flattenLists(params.tokens));
496
- cache.codeBlockAndSpanRanges(
497
- helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())
498
- );
565
+ const lineMetadata =
566
+ helpers.getLineMetadata(paramsBase);
567
+ const codeBlockAndSpanRanges =
568
+ helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata);
569
+ const flattenedLists =
570
+ helpers.flattenLists(paramsBase.tokens);
571
+ const htmlElementRanges =
572
+ helpers.htmlElementRanges(paramsBase, lineMetadata);
573
+ const referenceLinkImageData =
574
+ helpers.getReferenceLinkImageData(lineMetadata);
575
+ cache.set({
576
+ codeBlockAndSpanRanges,
577
+ flattenedLists,
578
+ htmlElementRanges,
579
+ lineMetadata,
580
+ referenceLinkImageData
581
+ });
499
582
  // Function to run for each rule
500
583
  let results = [];
501
584
  // eslint-disable-next-line jsdoc/require-jsdoc
502
585
  function forRule(rule) {
503
586
  // Configure rule
504
587
  const ruleName = rule.names[0].toUpperCase();
505
- params.config = effectiveConfig[ruleName];
588
+ const params = {
589
+ ...paramsBase,
590
+ "config": effectiveConfig[ruleName]
591
+ };
506
592
  // eslint-disable-next-line jsdoc/require-jsdoc
507
593
  function throwError(property) {
508
594
  throw new Error(
@@ -697,6 +783,7 @@ function lintContent(
697
783
  * @param {string} file Path of file to lint.
698
784
  * @param {Object} md Instance of markdown-it.
699
785
  * @param {Configuration} config Configuration object.
786
+ * @param {ConfigurationParser[] | null} configParsers Configuration parsers.
700
787
  * @param {RegExp} frontMatter Regular expression for front matter.
701
788
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
702
789
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
@@ -711,6 +798,7 @@ function lintFile(
711
798
  file,
712
799
  md,
713
800
  config,
801
+ configParsers,
714
802
  frontMatter,
715
803
  handleRuleFailures,
716
804
  noInlineConfig,
@@ -723,8 +811,19 @@ function lintFile(
723
811
  if (err) {
724
812
  return callback(err);
725
813
  }
726
- return lintContent(ruleList, file, content, md, config, frontMatter,
727
- handleRuleFailures, noInlineConfig, resultVersion, callback);
814
+ return lintContent(
815
+ ruleList,
816
+ file,
817
+ content,
818
+ md,
819
+ config,
820
+ configParsers,
821
+ frontMatter,
822
+ handleRuleFailures,
823
+ noInlineConfig,
824
+ resultVersion,
825
+ callback
826
+ );
728
827
  }
729
828
  // Make a/synchronous call to read file
730
829
  if (synchronous) {
@@ -750,7 +849,8 @@ function lintInput(options, synchronous, callback) {
750
849
  const ruleList = rules.concat(options.customRules || []);
751
850
  const ruleErr = validateRuleList(ruleList, synchronous);
752
851
  if (ruleErr) {
753
- return callback(ruleErr);
852
+ callback(ruleErr);
853
+ return;
754
854
  }
755
855
  let files = [];
756
856
  if (Array.isArray(options.files)) {
@@ -761,18 +861,19 @@ function lintInput(options, synchronous, callback) {
761
861
  const strings = options.strings || {};
762
862
  const stringsKeys = Object.keys(strings);
763
863
  const config = options.config || { "default": true };
864
+ const configParsers = options.configParsers || null;
764
865
  const frontMatter = (options.frontMatter === undefined) ?
765
866
  helpers.frontMatterRe : options.frontMatter;
766
867
  const handleRuleFailures = !!options.handleRuleFailures;
767
868
  const noInlineConfig = !!options.noInlineConfig;
768
869
  const resultVersion = (options.resultVersion === undefined) ?
769
- 2 : options.resultVersion;
870
+ 3 : options.resultVersion;
770
871
  const md = markdownIt({ "html": true });
771
872
  const markdownItPlugins = options.markdownItPlugins || [];
772
- markdownItPlugins.forEach(function forPlugin(plugin) {
873
+ for (const plugin of markdownItPlugins) {
773
874
  // @ts-ignore
774
875
  md.use(...plugin);
775
- });
876
+ }
776
877
  const fs = options.fs || require("fs");
777
878
  const results = newResults(ruleList);
778
879
  let done = false;
@@ -804,6 +905,7 @@ function lintInput(options, synchronous, callback) {
804
905
  currentItem,
805
906
  md,
806
907
  config,
908
+ configParsers,
807
909
  frontMatter,
808
910
  handleRuleFailures,
809
911
  noInlineConfig,
@@ -812,16 +914,16 @@ function lintInput(options, synchronous, callback) {
812
914
  synchronous,
813
915
  lintWorkerCallback
814
916
  );
815
- } else if (stringsKeys.length > 0) {
917
+ } else if ((currentItem = stringsKeys.shift())) {
816
918
  // Lint next string
817
919
  concurrency++;
818
- currentItem = stringsKeys.shift();
819
920
  lintContent(
820
921
  ruleList,
821
922
  currentItem,
822
923
  strings[currentItem] || "",
823
924
  md,
824
925
  config,
926
+ configParsers,
825
927
  frontMatter,
826
928
  handleRuleFailures,
827
929
  noInlineConfig,
@@ -852,7 +954,6 @@ function lintInput(options, synchronous, callback) {
852
954
  lintWorker();
853
955
  lintWorker();
854
956
  }
855
- return null;
856
957
  }
857
958
 
858
959
  /**
@@ -875,6 +976,7 @@ const markdownlintPromisify = promisify && promisify(markdownlint);
875
976
  * @returns {Promise<LintResults>} Results object.
876
977
  */
877
978
  function markdownlintPromise(options) {
979
+ // @ts-ignore
878
980
  return markdownlintPromisify(options);
879
981
  }
880
982
 
@@ -885,49 +987,17 @@ function markdownlintPromise(options) {
885
987
  * @returns {LintResults} Results object.
886
988
  */
887
989
  function markdownlintSync(options) {
888
- let results = null;
990
+ let results = {};
889
991
  lintInput(options, true, function callback(error, res) {
890
992
  if (error) {
891
993
  throw error;
892
994
  }
893
995
  results = res;
894
996
  });
997
+ // @ts-ignore
895
998
  return results;
896
999
  }
897
1000
 
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
1001
  /**
932
1002
  * Resolve referenced "extends" path in a configuration file
933
1003
  * using path.resolve() with require.resolve() as a fallback.
@@ -935,7 +1005,7 @@ function parseConfiguration(name, content, parsers) {
935
1005
  * @param {string} configFile Configuration file name.
936
1006
  * @param {string} referenceId Referenced identifier to resolve.
937
1007
  * @param {Object} fs File system implementation.
938
- * @param {ResolveConfigExtendsCallback} [callback] Callback (err, result)
1008
+ * @param {ResolveConfigExtendsCallback} callback Callback (err, result)
939
1009
  * function.
940
1010
  * @returns {void}
941
1011
  */
@@ -1005,6 +1075,7 @@ function readConfig(file, parsers, fs, callback) {
1005
1075
  } else {
1006
1076
  // @ts-ignore
1007
1077
  callback = parsers;
1078
+ // @ts-ignore
1008
1079
  parsers = null;
1009
1080
  }
1010
1081
  }
@@ -1012,14 +1083,18 @@ function readConfig(file, parsers, fs, callback) {
1012
1083
  fs = require("fs");
1013
1084
  }
1014
1085
  // Read file
1086
+ const os = require("os");
1087
+ file = helpers.expandTildePath(file, os);
1015
1088
  fs.readFile(file, "utf8", (err, content) => {
1016
1089
  if (err) {
1090
+ // @ts-ignore
1017
1091
  return callback(err);
1018
1092
  }
1019
1093
  // Try to parse file
1020
1094
  // @ts-ignore
1021
1095
  const { config, message } = parseConfiguration(file, content, parsers);
1022
1096
  if (!config) {
1097
+ // @ts-ignore
1023
1098
  return callback(new Error(message));
1024
1099
  }
1025
1100
  // Extend configuration
@@ -1028,16 +1103,19 @@ function readConfig(file, parsers, fs, callback) {
1028
1103
  delete config.extends;
1029
1104
  return resolveConfigExtends(
1030
1105
  file,
1031
- configExtends,
1106
+ helpers.expandTildePath(configExtends, os),
1032
1107
  fs,
1033
1108
  (_, resolvedExtends) => readConfig(
1109
+ // @ts-ignore
1034
1110
  resolvedExtends,
1035
1111
  parsers,
1036
1112
  fs,
1037
1113
  (errr, extendsConfig) => {
1038
1114
  if (errr) {
1115
+ // @ts-ignore
1039
1116
  return callback(errr);
1040
1117
  }
1118
+ // @ts-ignore
1041
1119
  return callback(null, {
1042
1120
  ...extendsConfig,
1043
1121
  ...config
@@ -1046,6 +1124,7 @@ function readConfig(file, parsers, fs, callback) {
1046
1124
  )
1047
1125
  );
1048
1126
  }
1127
+ // @ts-ignore
1049
1128
  return callback(null, config);
1050
1129
  });
1051
1130
  }
@@ -1079,6 +1158,8 @@ function readConfigSync(file, parsers, fs) {
1079
1158
  fs = require("fs");
1080
1159
  }
1081
1160
  // Read file
1161
+ const os = require("os");
1162
+ file = helpers.expandTildePath(file, os);
1082
1163
  const content = fs.readFileSync(file, "utf8");
1083
1164
  // Try to parse file
1084
1165
  const { config, message } = parseConfiguration(file, content, parsers);
@@ -1089,7 +1170,11 @@ function readConfigSync(file, parsers, fs) {
1089
1170
  const configExtends = config.extends;
1090
1171
  if (configExtends) {
1091
1172
  delete config.extends;
1092
- const resolvedExtends = resolveConfigExtendsSync(file, configExtends, fs);
1173
+ const resolvedExtends = resolveConfigExtendsSync(
1174
+ file,
1175
+ helpers.expandTildePath(configExtends, os),
1176
+ fs
1177
+ );
1093
1178
  return {
1094
1179
  ...readConfigSync(resolvedExtends, parsers, fs),
1095
1180
  ...config
@@ -1206,16 +1291,17 @@ module.exports = markdownlint;
1206
1291
  * Configuration options.
1207
1292
  *
1208
1293
  * @typedef {Object} Options
1209
- * @property {string[] | string} [files] Files to lint.
1210
- * @property {Object.<string, string>} [strings] Strings to lint.
1211
1294
  * @property {Configuration} [config] Configuration object.
1295
+ * @property {ConfigurationParser[]} [configParsers] Configuration parsers.
1212
1296
  * @property {Rule[] | Rule} [customRules] Custom rules.
1297
+ * @property {string[] | string} [files] Files to lint.
1213
1298
  * @property {RegExp} [frontMatter] Front matter pattern.
1299
+ * @property {Object} [fs] File system implementation.
1214
1300
  * @property {boolean} [handleRuleFailures] True to catch exceptions.
1301
+ * @property {Plugin[]} [markdownItPlugins] Additional plugins.
1215
1302
  * @property {boolean} [noInlineConfig] True to ignore HTML directives.
1216
1303
  * @property {number} [resultVersion] Results object version.
1217
- * @property {Plugin[]} [markdownItPlugins] Additional plugins.
1218
- * @property {Object} [fs] File system implementation.
1304
+ * @property {Object.<string, string>} [strings] Strings to lint.
1219
1305
  */
1220
1306
 
1221
1307
  /**