markdownlint 0.21.0 → 0.23.1

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.
@@ -12,12 +12,12 @@ module.exports.newLineRe = newLineRe;
12
12
  // Regular expression for matching common front matter (YAML and TOML)
13
13
  module.exports.frontMatterRe =
14
14
  // eslint-disable-next-line max-len
15
- /((^---\s*$[^]*?^---\s*$)|(^\+\+\+\s*$[^]*?^(\+\+\+|\.\.\.)\s*$))(\r\n|\r|\n|$)/m;
15
+ /((^---\s*$[^]*?^---\s*$)|(^\+\+\+\s*$[^]*?^(\+\+\+|\.\.\.)\s*$)|(^\{\s*$[^]*?^\}\s*$))(\r\n|\r|\n|$)/m;
16
16
 
17
17
  // Regular expression for matching inline disable/enable comments
18
18
  const inlineCommentRe =
19
19
  // eslint-disable-next-line max-len
20
- /<!--\s*markdownlint-(?:(?:(disable|enable|capture|restore|disable-file|enable-file)((?:\s+[a-z0-9_-]+)*))|(?:(configure-file)\s+([\s\S]*?)))\s*-->/ig;
20
+ /<!--\s*markdownlint-(?:(?:(disable|enable|capture|restore|disable-file|enable-file|disable-next-line)((?:\s+[a-z0-9_-]+)*))|(?:(configure-file)\s+([\s\S]*?)))\s*-->/ig;
21
21
  module.exports.inlineCommentRe = inlineCommentRe;
22
22
 
23
23
  // Regular expressions for range matching
@@ -35,7 +35,11 @@ const linkRe = /\[(?:[^[\]]|\[[^\]]*\])*\](?:\(\S*\))?/g;
35
35
  module.exports.utf8Encoding = "utf8";
36
36
 
37
37
  // All punctuation characters (normal and full-width)
38
- module.exports.allPunctuation = ".,;:!?。,;:!?";
38
+ const allPunctuation = ".,;:!?。,;:!?";
39
+ module.exports.allPunctuation = allPunctuation;
40
+
41
+ // All punctuation characters without question mark (normal and full-width)
42
+ module.exports.allPunctuationNoQuestion = allPunctuation.replace(/[??]/gu, "");
39
43
 
40
44
  // Returns true iff the input is a number
41
45
  module.exports.isNumber = function isNumber(obj) {
@@ -61,6 +65,8 @@ module.exports.isObject = function isObject(obj) {
61
65
  // Example: Contains nothing, whitespace, or comments
62
66
  const blankLineRe = />|(?:<!--.*?-->)/g;
63
67
  module.exports.isBlankLine = function isBlankLine(line) {
68
+ // Call to String.replace follows best practices and is not a security check
69
+ // False-positive for js/incomplete-multi-character-sanitization
64
70
  return !line || !line.trim() || !line.replace(blankLineRe, "").trim();
65
71
  };
66
72
 
@@ -93,32 +99,47 @@ module.exports.includesSorted = function includesSorted(array, element) {
93
99
  return false;
94
100
  };
95
101
 
96
- // Replaces the text of all properly-formatted HTML comments with whitespace
102
+ // Replaces the content of properly-formatted CommonMark comments with "."
97
103
  // This preserves the line/column information for the rest of the document
98
- // Trailing whitespace is avoided with a '\' character in the last column
99
- // See https://www.w3.org/TR/html5/syntax.html#comments for details
104
+ // https://spec.commonmark.org/0.29/#html-blocks
105
+ // https://spec.commonmark.org/0.29/#html-comment
100
106
  const htmlCommentBegin = "<!--";
101
107
  const htmlCommentEnd = "-->";
102
108
  module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
103
109
  let i = 0;
104
110
  while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
105
- const j = text.indexOf(htmlCommentEnd, i);
111
+ const j = text.indexOf(htmlCommentEnd, i + 2);
106
112
  if (j === -1) {
107
113
  // Un-terminated comments are treated as text
108
114
  break;
109
115
  }
110
- const comment = text.slice(i + htmlCommentBegin.length, j);
111
- if ((comment.length > 0) &&
112
- (comment[0] !== ">") &&
113
- (comment[comment.length - 1] !== "-") &&
114
- !comment.includes("--") &&
115
- (text.slice(i, j + htmlCommentEnd.length)
116
- .search(inlineCommentRe) === -1)) {
117
- const blanks = comment
118
- .replace(/[^\r\n]/g, " ")
119
- .replace(/ ([\r\n])/g, "\\$1");
120
- text = text.slice(0, i + htmlCommentBegin.length) +
121
- blanks + text.slice(j);
116
+ // If the comment has content...
117
+ if (j > i + htmlCommentBegin.length) {
118
+ let k = i - 1;
119
+ while (text[k] === " ") {
120
+ k--;
121
+ }
122
+ // If comment is not within an indented code block...
123
+ if (k >= i - 4) {
124
+ const content = text.slice(i + htmlCommentBegin.length, j);
125
+ const isBlock = (k < 0) || (text[k] === "\n");
126
+ const isValid = isBlock ||
127
+ (!content.startsWith(">") && !content.startsWith("->") &&
128
+ !content.endsWith("-") && !content.includes("--"));
129
+ // If a valid block/inline comment...
130
+ if (isValid) {
131
+ const inlineCommentIndex = text
132
+ .slice(i, j + htmlCommentEnd.length)
133
+ .search(inlineCommentRe);
134
+ // If not a markdownlint inline directive...
135
+ if (inlineCommentIndex === -1) {
136
+ text =
137
+ text.slice(0, i + htmlCommentBegin.length) +
138
+ content.replace(/[^\r\n]/g, ".") +
139
+ text.slice(j);
140
+ }
141
+ }
142
+ }
122
143
  }
123
144
  i = j + htmlCommentEnd.length;
124
145
  }
@@ -167,7 +188,7 @@ module.exports.fencedCodeBlockStyleFor =
167
188
  */
168
189
  function indentFor(token) {
169
190
  const line = token.line.replace(/^[\s>]*(> |>)/, "");
170
- return line.length - line.trimLeft().length;
191
+ return line.length - line.trimStart().length;
171
192
  }
172
193
  module.exports.indentFor = indentFor;
173
194
 
@@ -217,45 +238,65 @@ function filterTokens(params, type, handler) {
217
238
  }
218
239
  module.exports.filterTokens = filterTokens;
219
240
 
241
+ /**
242
+ * Returns whether a token is a math block (created by markdown-it-texmath).
243
+ *
244
+ * @param {Object} token MarkdownItToken instance.
245
+ * @returns {boolean} True iff token is a math block.
246
+ */
247
+ function isMathBlock(token) {
248
+ return (
249
+ (token.tag === "math") &&
250
+ token.type.startsWith("math_block") &&
251
+ !token.type.endsWith("_end")
252
+ );
253
+ }
254
+
220
255
  // Get line metadata array
221
256
  module.exports.getLineMetadata = function getLineMetadata(params) {
222
- const lineMetadata = params.lines.map(function mapLine(line, index) {
223
- return [ line, index, false, 0, false, false ];
224
- });
225
- filterTokens(params, "fence", function forToken(token) {
257
+ const lineMetadata = params.lines.map(
258
+ (line, index) => [ line, index, false, 0, false, false, false, false ]
259
+ );
260
+ filterTokens(params, "fence", (token) => {
226
261
  lineMetadata[token.map[0]][3] = 1;
227
262
  lineMetadata[token.map[1] - 1][3] = -1;
228
263
  for (let i = token.map[0] + 1; i < token.map[1] - 1; i++) {
229
264
  lineMetadata[i][2] = true;
230
265
  }
231
266
  });
232
- filterTokens(params, "code_block", function forToken(token) {
267
+ filterTokens(params, "code_block", (token) => {
233
268
  for (let i = token.map[0]; i < token.map[1]; i++) {
234
269
  lineMetadata[i][2] = true;
235
270
  }
236
271
  });
237
- filterTokens(params, "table_open", function forToken(token) {
272
+ filterTokens(params, "table_open", (token) => {
238
273
  for (let i = token.map[0]; i < token.map[1]; i++) {
239
274
  lineMetadata[i][4] = true;
240
275
  }
241
276
  });
242
- filterTokens(params, "list_item_open", function forToken(token) {
277
+ filterTokens(params, "list_item_open", (token) => {
243
278
  let count = 1;
244
279
  for (let i = token.map[0]; i < token.map[1]; i++) {
245
280
  lineMetadata[i][5] = count;
246
281
  count++;
247
282
  }
248
283
  });
249
- filterTokens(params, "hr", function forToken(token) {
284
+ filterTokens(params, "hr", (token) => {
250
285
  lineMetadata[token.map[0]][6] = true;
251
286
  });
287
+ params.tokens.filter(isMathBlock).forEach((token) => {
288
+ for (let i = token.map[0]; i < token.map[1]; i++) {
289
+ lineMetadata[i][7] = true;
290
+ }
291
+ });
252
292
  return lineMetadata;
253
293
  };
254
294
 
255
295
  // Calls the provided function for each line (with context)
256
296
  module.exports.forEachLine = function forEachLine(lineMetadata, handler) {
257
297
  lineMetadata.forEach(function forMetadata(metadata) {
258
- // Parameters: line, lineIndex, inCode, onFence, inTable, inItem, inBreak
298
+ // Parameters:
299
+ // line, lineIndex, inCode, onFence, inTable, inItem, inBreak, inMath
259
300
  handler(...metadata);
260
301
  });
261
302
  };
@@ -268,7 +309,11 @@ module.exports.flattenLists = function flattenLists(params) {
268
309
  let nesting = 0;
269
310
  const nestingStack = [];
270
311
  let lastWithMap = { "map": [ 0, 1 ] };
271
- params.tokens.forEach(function forToken(token) {
312
+ params.tokens.forEach((token) => {
313
+ if (isMathBlock(token) && token.map[1]) {
314
+ // markdown-it-texmath plugin does not account for math_block_end
315
+ token.map[1]++;
316
+ }
272
317
  if ((token.type === "bullet_list_open") ||
273
318
  (token.type === "ordered_list_open")) {
274
319
  // Save current context and start a new one
@@ -355,6 +400,7 @@ function forEachInlineCodeSpan(input, handler) {
355
400
  let currentTicks = 0;
356
401
  let state = "normal";
357
402
  // Deliberate <= so trailing 0 completes the last span (ex: "text `code`")
403
+ // False-positive for js/index-out-of-bounds
358
404
  for (; index <= input.length; index++) {
359
405
  const char = input[index];
360
406
  // Ignore backticks in link destination
@@ -491,7 +537,10 @@ module.exports.frontMatterHasTitle =
491
537
  const ignoreFrontMatter =
492
538
  (frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern;
493
539
  const frontMatterTitleRe =
494
- new RegExp(String(frontMatterTitlePattern || "^\\s*title\\s*[:=]"), "i");
540
+ new RegExp(
541
+ String(frontMatterTitlePattern || "^\\s*\"?title\"?\\s*[:=]"),
542
+ "i"
543
+ );
495
544
  return !ignoreFrontMatter &&
496
545
  frontMatterLines.some((line) => frontMatterTitleRe.test(line));
497
546
  };
@@ -674,8 +723,9 @@ module.exports.applyFixes = function applyFixes(input, errors) {
674
723
  const editIndex = editColumn - 1;
675
724
  if (
676
725
  (lineIndex !== lastLineIndex) ||
677
- ((editIndex + deleteCount) < lastEditIndex) ||
678
- (deleteCount === -1)
726
+ (deleteCount === -1) ||
727
+ ((editIndex + deleteCount) <=
728
+ (lastEditIndex - ((deleteCount > 0) ? 0 : 1)))
679
729
  ) {
680
730
  lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
681
731
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint-rule-helpers",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "A collection of markdownlint helper functions for custom rules",
5
5
  "main": "helpers.js",
6
6
  "author": "David Anson (https://dlaa.me/)",
@@ -8,7 +8,7 @@ export = markdownlint;
8
8
  */
9
9
  declare function markdownlint(options: Options, callback: LintCallback): void;
10
10
  declare namespace markdownlint {
11
- export { markdownlintSync as sync, readConfig, readConfigSync, promises, RuleFunction, RuleParams, MarkdownItToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback };
11
+ export { markdownlintSync as sync, readConfig, readConfigSync, getVersion, promises, RuleFunction, RuleParams, MarkdownItToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback };
12
12
  }
13
13
  /**
14
14
  * Configuration options.
@@ -51,7 +51,7 @@ type Options = {
51
51
  /**
52
52
  * Additional plugins.
53
53
  */
54
- markdownItPlugins?: any[][];
54
+ markdownItPlugins?: Plugin[];
55
55
  };
56
56
  /**
57
57
  * Called with the result of the lint operation.
@@ -82,6 +82,12 @@ declare function readConfig(file: string, parsers: ConfigurationParser[] | ReadC
82
82
  * @returns {Configuration} Configuration object.
83
83
  */
84
84
  declare function readConfigSync(file: string, parsers?: ConfigurationParser[]): Configuration;
85
+ /**
86
+ * Gets the (semantic) version of the library.
87
+ *
88
+ * @returns {string} SemVer string.
89
+ */
90
+ declare function getVersion(): string;
85
91
  declare namespace promises {
86
92
  export { markdownlintPromise as markdownlint };
87
93
  export { readConfigPromise as readConfig };
@@ -99,7 +105,7 @@ type RuleParams = {
99
105
  */
100
106
  name: string;
101
107
  /**
102
- * markdown-it token objects.
108
+ * Token objects from markdown-it.
103
109
  */
104
110
  tokens: MarkdownItToken[];
105
111
  /**
@@ -256,7 +262,7 @@ type Rule = {
256
262
  function: RuleFunction;
257
263
  };
258
264
  /**
259
- * markdown-it plugin.
265
+ * A markdown-it plugin.
260
266
  */
261
267
  type Plugin = any[];
262
268
  /**
@@ -304,7 +310,7 @@ type LintError = {
304
310
  /**
305
311
  * Fix information.
306
312
  */
307
- fixInfo: FixInfo;
313
+ fixInfo?: FixInfo;
308
314
  };
309
315
  /**
310
316
  * Fix information.
@@ -4,12 +4,17 @@
4
4
 
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
- const util = require("util");
7
+ const { promisify } = require("util");
8
8
  const markdownIt = require("markdown-it");
9
9
  const rules = require("./rules");
10
10
  const helpers = require("../helpers");
11
11
  const cache = require("./cache");
12
12
 
13
+ // @ts-ignore
14
+ // eslint-disable-next-line camelcase, max-len, no-inline-comments, no-undef
15
+ const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? require : /* c8 ignore next */ __non_webpack_require__;
16
+ // Capture native require implementation for dynamic loading of modules
17
+
13
18
  const deprecatedRuleNames = [ "MD002", "MD006" ];
14
19
 
15
20
  /**
@@ -51,10 +56,12 @@ function validateRuleList(ruleList) {
51
56
  result = newError(property);
52
57
  }
53
58
  });
54
- if (!result && rule.information) {
55
- if (Object.getPrototypeOf(rule.information) !== URL.prototype) {
56
- result = newError("information");
57
- }
59
+ if (
60
+ !result &&
61
+ rule.information &&
62
+ (Object.getPrototypeOf(rule.information) !== URL.prototype)
63
+ ) {
64
+ result = newError("information");
58
65
  }
59
66
  if (!result) {
60
67
  rule.names.forEach(function forName(name) {
@@ -176,18 +183,27 @@ function removeFrontMatter(content, frontMatter) {
176
183
  * @returns {void}
177
184
  */
178
185
  function annotateTokens(tokens, lines) {
179
- let tbodyMap = null;
186
+ let tableMap = null;
180
187
  tokens.forEach(function forToken(token) {
181
- // Handle missing maps for table body
182
- if (token.type === "tbody_open") {
183
- tbodyMap = token.map.slice();
184
- } else if ((token.type === "tr_close") && tbodyMap) {
185
- tbodyMap[0]++;
186
- } else if (token.type === "tbody_close") {
187
- tbodyMap = null;
188
+ // Handle missing maps for table head/body
189
+ if (
190
+ (token.type === "thead_open") ||
191
+ (token.type === "tbody_open")
192
+ ) {
193
+ tableMap = token.map.slice();
194
+ } else if (
195
+ (token.type === "tr_close") &&
196
+ tableMap
197
+ ) {
198
+ tableMap[0]++;
199
+ } else if (
200
+ (token.type === "thead_close") ||
201
+ (token.type === "tbody_close")
202
+ ) {
203
+ tableMap = null;
188
204
  }
189
- if (tbodyMap && !token.map) {
190
- token.map = tbodyMap.slice();
205
+ if (tableMap && !token.map) {
206
+ token.map = tableMap.slice();
191
207
  }
192
208
  // Update token metadata
193
209
  if (token.map) {
@@ -322,13 +338,13 @@ function getEnabledRulesPerLineNumber(
322
338
  // eslint-disable-next-line jsdoc/require-jsdoc
323
339
  function handleInlineConfig(perLine, forEachMatch, forEachLine) {
324
340
  const input = perLine ? lines : [ lines.join("\n") ];
325
- input.forEach((line) => {
341
+ input.forEach((line, lineIndex) => {
326
342
  if (!noInlineConfig) {
327
343
  let match = null;
328
344
  while ((match = helpers.inlineCommentRe.exec(line))) {
329
345
  const action = (match[1] || match[3]).toUpperCase();
330
346
  const parameter = match[2] || match[4];
331
- forEachMatch(action, parameter);
347
+ forEachMatch(action, parameter, lineIndex + 1);
332
348
  }
333
349
  }
334
350
  if (forEachLine) {
@@ -345,28 +361,27 @@ function getEnabledRulesPerLineNumber(
345
361
  ...config,
346
362
  ...json
347
363
  };
348
- // eslint-disable-next-line unicorn/prefer-optional-catch-binding
349
- } catch (error) {
364
+ } catch {
350
365
  // Ignore parse errors for inline configuration
351
366
  }
352
367
  }
353
368
  }
354
369
  // eslint-disable-next-line jsdoc/require-jsdoc
355
- function applyEnableDisable(action, parameter) {
370
+ function applyEnableDisable(action, parameter, state) {
356
371
  const enabled = (action.startsWith("ENABLE"));
357
372
  const items = parameter ?
358
373
  parameter.trim().toUpperCase().split(/\s+/) :
359
374
  allRuleNames;
360
375
  items.forEach((nameUpper) => {
361
376
  (aliasToRuleNames[nameUpper] || []).forEach((ruleName) => {
362
- enabledRules[ruleName] = enabled;
377
+ state[ruleName] = enabled;
363
378
  });
364
379
  });
365
380
  }
366
381
  // eslint-disable-next-line jsdoc/require-jsdoc
367
382
  function enableDisableFile(action, parameter) {
368
383
  if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
369
- applyEnableDisable(action, parameter);
384
+ applyEnableDisable(action, parameter, enabledRules);
370
385
  }
371
386
  }
372
387
  // eslint-disable-next-line jsdoc/require-jsdoc
@@ -377,12 +392,22 @@ function getEnabledRulesPerLineNumber(
377
392
  enabledRules = { ...capturedRules };
378
393
  } else if ((action === "ENABLE") || (action === "DISABLE")) {
379
394
  enabledRules = { ...enabledRules };
380
- applyEnableDisable(action, parameter);
395
+ applyEnableDisable(action, parameter, enabledRules);
381
396
  }
382
397
  }
383
398
  // eslint-disable-next-line jsdoc/require-jsdoc
384
399
  function updateLineState() {
385
- enabledRulesPerLineNumber.push(enabledRules);
400
+ enabledRulesPerLineNumber.push({ ...enabledRules });
401
+ }
402
+ // eslint-disable-next-line jsdoc/require-jsdoc
403
+ function disableNextLine(action, parameter, lineNumber) {
404
+ if (action === "DISABLE-NEXT-LINE") {
405
+ applyEnableDisable(
406
+ action,
407
+ parameter,
408
+ enabledRulesPerLineNumber[lineNumber + 1] || {}
409
+ );
410
+ }
386
411
  }
387
412
  // Handle inline comments
388
413
  handleInlineConfig(false, configureFile);
@@ -396,6 +421,7 @@ function getEnabledRulesPerLineNumber(
396
421
  capturedRules = enabledRules;
397
422
  handleInlineConfig(true, enableDisableFile);
398
423
  handleInlineConfig(true, captureRestoreEnableDisable, updateLineState);
424
+ handleInlineConfig(true, disableNextLine);
399
425
  // Return results
400
426
  return {
401
427
  effectiveConfig,
@@ -440,8 +466,8 @@ function uniqueFilterForSortedErrors(value, index, array) {
440
466
  *
441
467
  * @param {Rule[]} ruleList List of rules.
442
468
  * @param {string} name Identifier for the content.
443
- * @param {string} content Markdown content
444
- * @param {Object} md markdown-it instance.
469
+ * @param {string} content Markdown content.
470
+ * @param {Object} md Instance of markdown-it.
445
471
  * @param {Configuration} config Configuration object.
446
472
  * @param {RegExp} frontMatter Regular expression for front matter.
447
473
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
@@ -652,7 +678,7 @@ function lintContent(
652
678
  *
653
679
  * @param {Rule[]} ruleList List of rules.
654
680
  * @param {string} file Path of file to lint.
655
- * @param {Object} md markdown-it instance.
681
+ * @param {Object} md Instance of markdown-it.
656
682
  * @param {Configuration} config Configuration object.
657
683
  * @param {RegExp} frontMatter Regular expression for front matter.
658
684
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
@@ -702,6 +728,7 @@ function lintInput(options, synchronous, callback) {
702
728
  // Normalize inputs
703
729
  options = options || {};
704
730
  callback = callback || function noop() {};
731
+ // eslint-disable-next-line unicorn/prefer-spread
705
732
  const ruleList = rules.concat(options.customRules || []);
706
733
  const ruleErr = validateRuleList(ruleList);
707
734
  if (ruleErr) {
@@ -834,7 +861,7 @@ function markdownlint(options, callback) {
834
861
  return lintInput(options, false, callback);
835
862
  }
836
863
 
837
- const markdownlintPromisify = util.promisify(markdownlint);
864
+ const markdownlintPromisify = promisify && promisify(markdownlint);
838
865
 
839
866
  /**
840
867
  * Lint specified Markdown files.
@@ -895,6 +922,35 @@ function parseConfiguration(name, content, parsers) {
895
922
  };
896
923
  }
897
924
 
925
+ /**
926
+ * Resolve referenced "extends" path in a configuration file
927
+ * using path.resolve() with require.resolve() as a fallback.
928
+ *
929
+ * @param {string} configFile Configuration file name.
930
+ * @param {string} referenceId Referenced identifier to resolve.
931
+ * @returns {string} Resolved path to file.
932
+ */
933
+ function resolveConfigExtends(configFile, referenceId) {
934
+ const configFileDirname = path.dirname(configFile);
935
+ const resolvedExtendsFile = path.resolve(configFileDirname, referenceId);
936
+ try {
937
+ if (fs.statSync(resolvedExtendsFile).isFile()) {
938
+ return resolvedExtendsFile;
939
+ }
940
+ } catch {
941
+ // If not a file or fs.statSync throws, try require.resolve
942
+ }
943
+ try {
944
+ return dynamicRequire.resolve(
945
+ referenceId,
946
+ { "paths": [ configFileDirname ] }
947
+ );
948
+ } catch {
949
+ // If require.resolve throws, return resolvedExtendsFile
950
+ }
951
+ return resolvedExtendsFile;
952
+ }
953
+
898
954
  /**
899
955
  * Read specified configuration file.
900
956
  *
@@ -925,8 +981,8 @@ function readConfig(file, parsers, callback) {
925
981
  const configExtends = config.extends;
926
982
  if (configExtends) {
927
983
  delete config.extends;
928
- const extendsFile = path.resolve(path.dirname(file), configExtends);
929
- return readConfig(extendsFile, parsers, (errr, extendsConfig) => {
984
+ const resolvedExtends = resolveConfigExtends(file, configExtends);
985
+ return readConfig(resolvedExtends, parsers, (errr, extendsConfig) => {
930
986
  if (errr) {
931
987
  return callback(errr);
932
988
  }
@@ -940,7 +996,7 @@ function readConfig(file, parsers, callback) {
940
996
  });
941
997
  }
942
998
 
943
- const readConfigPromisify = util.promisify(readConfig);
999
+ const readConfigPromisify = promisify && promisify(readConfig);
944
1000
 
945
1001
  /**
946
1002
  * Read specified configuration file.
@@ -974,21 +1030,29 @@ function readConfigSync(file, parsers) {
974
1030
  const configExtends = config.extends;
975
1031
  if (configExtends) {
976
1032
  delete config.extends;
1033
+ const resolvedExtends = resolveConfigExtends(file, configExtends);
977
1034
  return {
978
- ...readConfigSync(
979
- path.resolve(path.dirname(file), configExtends),
980
- parsers
981
- ),
1035
+ ...readConfigSync(resolvedExtends, parsers),
982
1036
  ...config
983
1037
  };
984
1038
  }
985
1039
  return config;
986
1040
  }
987
1041
 
1042
+ /**
1043
+ * Gets the (semantic) version of the library.
1044
+ *
1045
+ * @returns {string} SemVer string.
1046
+ */
1047
+ function getVersion() {
1048
+ return require("../package.json").version;
1049
+ }
1050
+
988
1051
  // Export a/synchronous/Promise APIs
989
1052
  markdownlint.sync = markdownlintSync;
990
1053
  markdownlint.readConfig = readConfig;
991
1054
  markdownlint.readConfigSync = readConfigSync;
1055
+ markdownlint.getVersion = getVersion;
992
1056
  markdownlint.promises = {
993
1057
  "markdownlint": markdownlintPromise,
994
1058
  "readConfig": readConfigPromise
@@ -1011,7 +1075,7 @@ module.exports = markdownlint;
1011
1075
  *
1012
1076
  * @typedef {Object} RuleParams
1013
1077
  * @property {string} name File/string name.
1014
- * @property {MarkdownItToken[]} tokens markdown-it token objects.
1078
+ * @property {MarkdownItToken[]} tokens Token objects from markdown-it.
1015
1079
  * @property {string[]} lines File/string lines.
1016
1080
  * @property {string[]} frontMatterLines Front matter lines.
1017
1081
  * @property {RuleConfiguration} config Rule configuration.
@@ -1094,7 +1158,7 @@ module.exports = markdownlint;
1094
1158
  */
1095
1159
 
1096
1160
  /**
1097
- * markdown-it plugin.
1161
+ * A markdown-it plugin.
1098
1162
  *
1099
1163
  * @typedef {Array} Plugin
1100
1164
  */
@@ -1125,7 +1189,7 @@ module.exports = markdownlint;
1125
1189
  * @property {string} errorDetail Detail about the error.
1126
1190
  * @property {string} errorContext Context for the error.
1127
1191
  * @property {number[]} errorRange Column number (1-based) and length.
1128
- * @property {FixInfo} fixInfo Fix information.
1192
+ * @property {FixInfo} [fixInfo] Fix information.
1129
1193
  */
1130
1194
 
1131
1195
  /**
package/lib/md002.js CHANGED
@@ -6,7 +6,7 @@ const { addErrorDetailIf } = require("../helpers");
6
6
 
7
7
  module.exports = {
8
8
  "names": [ "MD002", "first-heading-h1", "first-header-h1" ],
9
- "description": "First heading should be a top level heading",
9
+ "description": "First heading should be a top-level heading",
10
10
  "tags": [ "headings", "headers" ],
11
11
  "function": function MD002(params, onError) {
12
12
  const level = Number(params.config.level || 1);