markdownlint 0.24.0 → 0.25.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.
@@ -5,6 +5,7 @@
5
5
  const path = require("path");
6
6
  const { promisify } = require("util");
7
7
  const markdownIt = require("markdown-it");
8
+ const { deprecatedRuleNames } = require("./constants");
8
9
  const rules = require("./rules");
9
10
  const helpers = require("../helpers");
10
11
  const cache = require("./cache");
@@ -14,15 +15,14 @@ const cache = require("./cache");
14
15
  const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? require : /* c8 ignore next */ __non_webpack_require__;
15
16
  // Capture native require implementation for dynamic loading of modules
16
17
 
17
- const deprecatedRuleNames = [ "MD002", "MD006" ];
18
-
19
18
  /**
20
19
  * Validate the list of rules for structure and reuse.
21
20
  *
22
21
  * @param {Rule[]} ruleList List of rules.
22
+ * @param {boolean} synchronous Whether to execute synchronously.
23
23
  * @returns {string} Error message if validation fails.
24
24
  */
25
- function validateRuleList(ruleList) {
25
+ function validateRuleList(ruleList, synchronous) {
26
26
  let result = null;
27
27
  if (ruleList.length === rules.length) {
28
28
  // No need to validate if only using built-in rules
@@ -62,6 +62,19 @@ function validateRuleList(ruleList) {
62
62
  ) {
63
63
  result = newError("information");
64
64
  }
65
+ if (
66
+ !result &&
67
+ (rule.asynchronous !== undefined) &&
68
+ (typeof rule.asynchronous !== "boolean")
69
+ ) {
70
+ result = newError("asynchronous");
71
+ }
72
+ if (!result && rule.asynchronous && synchronous) {
73
+ result = new Error(
74
+ "Custom rule " + rule.names.join("/") + " at index " + customIndex +
75
+ " is asynchronous and can not be used in a synchronous context."
76
+ );
77
+ }
65
78
  if (!result) {
66
79
  rule.names.forEach(function forName(name) {
67
80
  const nameUpper = name.toUpperCase();
@@ -182,27 +195,21 @@ function removeFrontMatter(content, frontMatter) {
182
195
  * @returns {void}
183
196
  */
184
197
  function annotateTokens(tokens, lines) {
185
- let tableMap = null;
198
+ let trMap = null;
186
199
  tokens.forEach(function forToken(token) {
187
- // Handle missing maps for table head/body
188
- if (
189
- (token.type === "thead_open") ||
190
- (token.type === "tbody_open")
191
- ) {
192
- tableMap = [ ...token.map ];
193
- } else if (
194
- (token.type === "tr_close") &&
195
- tableMap
196
- ) {
197
- tableMap[0]++;
198
- } else if (
199
- (token.type === "thead_close") ||
200
- (token.type === "tbody_close")
201
- ) {
202
- tableMap = null;
200
+ // Provide missing maps for table content
201
+ if (token.type === "tr_open") {
202
+ trMap = token.map;
203
+ } else if (token.type === "tr_close") {
204
+ trMap = null;
205
+ }
206
+ if (!token.map && trMap) {
207
+ token.map = [ ...trMap ];
203
208
  }
204
- if (tableMap && !token.map) {
205
- token.map = [ ...tableMap ];
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]++;
206
213
  }
207
214
  // Update token metadata
208
215
  if (token.map) {
@@ -335,8 +342,7 @@ function getEnabledRulesPerLineNumber(
335
342
  const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
336
343
  // Helper functions
337
344
  // eslint-disable-next-line jsdoc/require-jsdoc
338
- function handleInlineConfig(perLine, forEachMatch, forEachLine) {
339
- const input = perLine ? lines : [ lines.join("\n") ];
345
+ function handleInlineConfig(input, forEachMatch, forEachLine) {
340
346
  input.forEach((line, lineIndex) => {
341
347
  if (!noInlineConfig) {
342
348
  let match = null;
@@ -367,6 +373,7 @@ function getEnabledRulesPerLineNumber(
367
373
  }
368
374
  // eslint-disable-next-line jsdoc/require-jsdoc
369
375
  function applyEnableDisable(action, parameter, state) {
376
+ state = { ...state };
370
377
  const enabled = (action.startsWith("ENABLE"));
371
378
  const items = parameter ?
372
379
  parameter.trim().toUpperCase().split(/\s+/) :
@@ -376,40 +383,42 @@ function getEnabledRulesPerLineNumber(
376
383
  state[ruleName] = enabled;
377
384
  });
378
385
  });
386
+ return state;
379
387
  }
380
388
  // eslint-disable-next-line jsdoc/require-jsdoc
381
389
  function enableDisableFile(action, parameter) {
382
390
  if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
383
- applyEnableDisable(action, parameter, enabledRules);
391
+ enabledRules = applyEnableDisable(action, parameter, enabledRules);
384
392
  }
385
393
  }
386
394
  // eslint-disable-next-line jsdoc/require-jsdoc
387
395
  function captureRestoreEnableDisable(action, parameter) {
388
396
  if (action === "CAPTURE") {
389
- capturedRules = { ...enabledRules };
397
+ capturedRules = enabledRules;
390
398
  } else if (action === "RESTORE") {
391
- enabledRules = { ...capturedRules };
399
+ enabledRules = capturedRules;
392
400
  } else if ((action === "ENABLE") || (action === "DISABLE")) {
393
- enabledRules = { ...enabledRules };
394
- applyEnableDisable(action, parameter, enabledRules);
401
+ enabledRules = applyEnableDisable(action, parameter, enabledRules);
395
402
  }
396
403
  }
397
404
  // eslint-disable-next-line jsdoc/require-jsdoc
398
405
  function updateLineState() {
399
- enabledRulesPerLineNumber.push({ ...enabledRules });
406
+ enabledRulesPerLineNumber.push(enabledRules);
400
407
  }
401
408
  // eslint-disable-next-line jsdoc/require-jsdoc
402
409
  function disableNextLine(action, parameter, lineNumber) {
403
410
  if (action === "DISABLE-NEXT-LINE") {
404
- applyEnableDisable(
405
- action,
406
- parameter,
407
- enabledRulesPerLineNumber[lineNumber + 1] || {}
408
- );
411
+ const nextLineNumber = frontMatterLines.length + lineNumber + 1;
412
+ enabledRulesPerLineNumber[nextLineNumber] =
413
+ applyEnableDisable(
414
+ action,
415
+ parameter,
416
+ enabledRulesPerLineNumber[nextLineNumber] || {}
417
+ );
409
418
  }
410
419
  }
411
420
  // Handle inline comments
412
- handleInlineConfig(false, configureFile);
421
+ handleInlineConfig([ lines.join("\n") ], configureFile);
413
422
  const effectiveConfig = getEffectiveConfig(
414
423
  ruleList, config, aliasToRuleNames);
415
424
  ruleList.forEach((rule) => {
@@ -418,9 +427,9 @@ function getEnabledRulesPerLineNumber(
418
427
  enabledRules[ruleName] = !!effectiveConfig[ruleName];
419
428
  });
420
429
  capturedRules = enabledRules;
421
- handleInlineConfig(true, enableDisableFile);
422
- handleInlineConfig(true, captureRestoreEnableDisable, updateLineState);
423
- handleInlineConfig(true, disableNextLine);
430
+ handleInlineConfig(lines, enableDisableFile);
431
+ handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
432
+ handleInlineConfig(lines, disableNextLine);
424
433
  // Return results
425
434
  return {
426
435
  effectiveConfig,
@@ -428,38 +437,6 @@ function getEnabledRulesPerLineNumber(
428
437
  };
429
438
  }
430
439
 
431
- /**
432
- * Compare function for Array.prototype.sort for ascending order of errors.
433
- *
434
- * @param {LintError} a First error.
435
- * @param {LintError} b Second error.
436
- * @returns {number} Positive value if a>b, negative value if b<a, 0 otherwise.
437
- */
438
- function lineNumberComparison(a, b) {
439
- return a.lineNumber - b.lineNumber;
440
- }
441
-
442
- /**
443
- * Filter function to include everything.
444
- *
445
- * @returns {boolean} True.
446
- */
447
- function filterAllValues() {
448
- return true;
449
- }
450
-
451
- /**
452
- * Function to return unique values from a sorted errors array.
453
- *
454
- * @param {LintError} value Error instance.
455
- * @param {number} index Index in array.
456
- * @param {LintError[]} array Array of errors.
457
- * @returns {boolean} Filter value.
458
- */
459
- function uniqueFilterForSortedErrors(value, index, array) {
460
- return (index === 0) || (value.lineNumber > array[index - 1].lineNumber);
461
- }
462
-
463
440
  /**
464
441
  * Lints a string containing Markdown content.
465
442
  *
@@ -509,28 +486,28 @@ function lintContent(
509
486
  );
510
487
  // Create parameters for rules
511
488
  const params = {
512
- name,
513
- tokens,
514
- lines,
515
- frontMatterLines
489
+ "name": helpers.deepFreeze(name),
490
+ "tokens": helpers.deepFreeze(tokens),
491
+ "lines": helpers.deepFreeze(lines),
492
+ "frontMatterLines": helpers.deepFreeze(frontMatterLines)
516
493
  };
517
494
  cache.lineMetadata(helpers.getLineMetadata(params));
518
495
  cache.flattenedLists(helpers.flattenLists(params.tokens));
519
- cache.inlineCodeSpanRanges(helpers.inlineCodeSpanRanges(params.lines));
496
+ cache.codeBlockAndSpanRanges(
497
+ helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())
498
+ );
520
499
  // Function to run for each rule
521
- const result = (resultVersion === 0) ? {} : [];
500
+ let results = [];
522
501
  // eslint-disable-next-line jsdoc/require-jsdoc
523
502
  function forRule(rule) {
524
503
  // Configure rule
525
- const ruleNameFriendly = rule.names[0];
526
- const ruleName = ruleNameFriendly.toUpperCase();
504
+ const ruleName = rule.names[0].toUpperCase();
527
505
  params.config = effectiveConfig[ruleName];
528
506
  // eslint-disable-next-line jsdoc/require-jsdoc
529
507
  function throwError(property) {
530
508
  throw new Error(
531
509
  "Property '" + property + "' of onError parameter is incorrect.");
532
510
  }
533
- const errors = [];
534
511
  // eslint-disable-next-line jsdoc/require-jsdoc
535
512
  function onError(errorInfo) {
536
513
  if (!errorInfo ||
@@ -539,6 +516,10 @@ function lintContent(
539
516
  (errorInfo.lineNumber > lines.length)) {
540
517
  throwError("lineNumber");
541
518
  }
519
+ const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
520
+ if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
521
+ return;
522
+ }
542
523
  if (errorInfo.detail &&
543
524
  !helpers.isString(errorInfo.detail)) {
544
525
  throwError("detail");
@@ -549,12 +530,12 @@ function lintContent(
549
530
  }
550
531
  if (errorInfo.range &&
551
532
  (!Array.isArray(errorInfo.range) ||
552
- (errorInfo.range.length !== 2) ||
553
- !helpers.isNumber(errorInfo.range[0]) ||
554
- (errorInfo.range[0] < 1) ||
555
- !helpers.isNumber(errorInfo.range[1]) ||
556
- (errorInfo.range[1] < 1) ||
557
- ((errorInfo.range[0] + errorInfo.range[1] - 1) >
533
+ (errorInfo.range.length !== 2) ||
534
+ !helpers.isNumber(errorInfo.range[0]) ||
535
+ (errorInfo.range[0] < 1) ||
536
+ !helpers.isNumber(errorInfo.range[1]) ||
537
+ (errorInfo.range[1] < 1) ||
538
+ ((errorInfo.range[0] + errorInfo.range[1] - 1) >
558
539
  lines[errorInfo.lineNumber - 1].length))) {
559
540
  throwError("range");
560
541
  }
@@ -599,78 +580,114 @@ function lintContent(
599
580
  cleanFixInfo.insertText = fixInfo.insertText;
600
581
  }
601
582
  }
602
- errors.push({
603
- "lineNumber": errorInfo.lineNumber + frontMatterLines.length,
604
- "detail": errorInfo.detail || null,
605
- "context": errorInfo.context || null,
606
- "range": errorInfo.range ? [ ...errorInfo.range ] : null,
583
+ results.push({
584
+ lineNumber,
585
+ "ruleName": rule.names[0],
586
+ "ruleNames": rule.names,
587
+ "ruleDescription": rule.description,
588
+ "ruleInformation": rule.information ? rule.information.href : null,
589
+ "errorDetail": errorInfo.detail || null,
590
+ "errorContext": errorInfo.context || null,
591
+ "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
607
592
  "fixInfo": fixInfo ? cleanFixInfo : null
608
593
  });
609
594
  }
610
- // Call (possibly external) rule function
611
- if (handleRuleFailures) {
612
- try {
613
- rule.function(params, onError);
614
- } catch (error) {
615
- onError({
616
- "lineNumber": 1,
617
- "detail": `This rule threw an exception: ${error.message}`
618
- });
595
+ // Call (possibly external) rule function to report errors
596
+ const catchCallsOnError = (error) => onError({
597
+ "lineNumber": 1,
598
+ "detail": `This rule threw an exception: ${error.message || error}`
599
+ });
600
+ const invokeRuleFunction = () => rule.function(params, onError);
601
+ if (rule.asynchronous) {
602
+ // Asynchronous rule, ensure it returns a Promise
603
+ const ruleFunctionPromise =
604
+ Promise.resolve().then(invokeRuleFunction);
605
+ return handleRuleFailures ?
606
+ ruleFunctionPromise.catch(catchCallsOnError) :
607
+ ruleFunctionPromise;
608
+ }
609
+ // Synchronous rule
610
+ try {
611
+ invokeRuleFunction();
612
+ } catch (error) {
613
+ if (handleRuleFailures) {
614
+ catchCallsOnError(error);
615
+ } else {
616
+ throw error;
619
617
  }
620
- } else {
621
- rule.function(params, onError);
622
618
  }
623
- // Record any errors (significant performance benefit from length check)
624
- if (errors.length > 0) {
625
- errors.sort(lineNumberComparison);
626
- const filteredErrors = errors
627
- .filter((resultVersion === 3) ?
628
- filterAllValues :
629
- uniqueFilterForSortedErrors)
630
- .filter(function removeDisabledRules(error) {
631
- return enabledRulesPerLineNumber[error.lineNumber][ruleName];
632
- })
633
- .map(function formatResults(error) {
634
- if (resultVersion === 0) {
635
- return error.lineNumber;
636
- }
637
- const errorObject = {};
638
- errorObject.lineNumber = error.lineNumber;
639
- if (resultVersion === 1) {
640
- errorObject.ruleName = ruleNameFriendly;
641
- errorObject.ruleAlias = rule.names[1] || rule.names[0];
642
- } else {
643
- errorObject.ruleNames = rule.names;
644
- }
645
- errorObject.ruleDescription = rule.description;
646
- errorObject.ruleInformation =
647
- rule.information ? rule.information.href : null;
648
- errorObject.errorDetail = error.detail;
649
- errorObject.errorContext = error.context;
650
- errorObject.errorRange = error.range;
651
- if (resultVersion === 3) {
652
- errorObject.fixInfo = error.fixInfo;
653
- }
654
- return errorObject;
655
- });
656
- if (filteredErrors.length > 0) {
657
- if (resultVersion === 0) {
658
- result[ruleNameFriendly] = filteredErrors;
659
- } else {
660
- Array.prototype.push.apply(result, filteredErrors);
661
- }
619
+ return null;
620
+ }
621
+ // eslint-disable-next-line jsdoc/require-jsdoc
622
+ function formatResults() {
623
+ // Sort results by rule name by line number
624
+ results.sort((a, b) => (
625
+ a.ruleName.localeCompare(b.ruleName) ||
626
+ a.lineNumber - b.lineNumber
627
+ ));
628
+ if (resultVersion < 3) {
629
+ // Remove fixInfo and multiple errors for the same rule and line number
630
+ const noPrevious = {
631
+ "ruleName": null,
632
+ "lineNumber": -1
633
+ };
634
+ results = results.filter((error, index, array) => {
635
+ delete error.fixInfo;
636
+ const previous = array[index - 1] || noPrevious;
637
+ return (
638
+ (error.ruleName !== previous.ruleName) ||
639
+ (error.lineNumber !== previous.lineNumber)
640
+ );
641
+ });
642
+ }
643
+ if (resultVersion === 0) {
644
+ // Return a dictionary of rule->[line numbers]
645
+ const dictionary = {};
646
+ for (const error of results) {
647
+ const ruleLines = dictionary[error.ruleName] || [];
648
+ ruleLines.push(error.lineNumber);
649
+ dictionary[error.ruleName] = ruleLines;
650
+ }
651
+ // @ts-ignore
652
+ results = dictionary;
653
+ } else if (resultVersion === 1) {
654
+ // Use ruleAlias instead of ruleNames
655
+ for (const error of results) {
656
+ error.ruleAlias = error.ruleNames[1] || error.ruleName;
657
+ delete error.ruleNames;
658
+ }
659
+ } else {
660
+ // resultVersion 2 or 3: Remove unwanted ruleName
661
+ for (const error of results) {
662
+ delete error.ruleName;
662
663
  }
663
664
  }
665
+ return results;
664
666
  }
665
667
  // Run all rules
668
+ const ruleListAsync = ruleList.filter((rule) => rule.asynchronous);
669
+ const ruleListSync = ruleList.filter((rule) => !rule.asynchronous);
670
+ const ruleListAsyncFirst = [
671
+ ...ruleListAsync,
672
+ ...ruleListSync
673
+ ];
674
+ const callbackSuccess = () => callback(null, formatResults());
675
+ const callbackError =
676
+ (error) => callback(error instanceof Error ? error : new Error(error));
666
677
  try {
667
- ruleList.forEach(forRule);
678
+ const ruleResults = ruleListAsyncFirst.map(forRule);
679
+ if (ruleListAsync.length > 0) {
680
+ Promise.all(ruleResults.slice(0, ruleListAsync.length))
681
+ .then(callbackSuccess)
682
+ .catch(callbackError);
683
+ } else {
684
+ callbackSuccess();
685
+ }
668
686
  } catch (error) {
687
+ callbackError(error);
688
+ } finally {
669
689
  cache.clear();
670
- return callback(error);
671
690
  }
672
- cache.clear();
673
- return callback(null, result);
674
691
  }
675
692
 
676
693
  /**
@@ -731,7 +748,7 @@ function lintInput(options, synchronous, callback) {
731
748
  callback = callback || function noop() {};
732
749
  // eslint-disable-next-line unicorn/prefer-spread
733
750
  const ruleList = rules.concat(options.customRules || []);
734
- const ruleErr = validateRuleList(ruleList);
751
+ const ruleErr = validateRuleList(ruleList, synchronous);
735
752
  if (ruleErr) {
736
753
  return callback(ruleErr);
737
754
  }
@@ -759,37 +776,32 @@ function lintInput(options, synchronous, callback) {
759
776
  const fs = options.fs || require("fs");
760
777
  const results = newResults(ruleList);
761
778
  let done = false;
762
- // Linting of strings is always synchronous
763
- let syncItem = null;
779
+ let concurrency = 0;
764
780
  // eslint-disable-next-line jsdoc/require-jsdoc
765
- function syncCallback(err, result) {
766
- if (err) {
767
- done = true;
768
- return callback(err);
781
+ function lintWorker() {
782
+ let currentItem = null;
783
+ // eslint-disable-next-line jsdoc/require-jsdoc
784
+ function lintWorkerCallback(err, result) {
785
+ concurrency--;
786
+ if (err) {
787
+ done = true;
788
+ return callback(err);
789
+ }
790
+ results[currentItem] = result;
791
+ if (!synchronous) {
792
+ lintWorker();
793
+ }
794
+ return null;
769
795
  }
770
- results[syncItem] = result;
771
- return null;
772
- }
773
- while (!done && (syncItem = stringsKeys.shift())) {
774
- lintContent(
775
- ruleList,
776
- syncItem,
777
- strings[syncItem] || "",
778
- md,
779
- config,
780
- frontMatter,
781
- handleRuleFailures,
782
- noInlineConfig,
783
- resultVersion,
784
- syncCallback
785
- );
786
- }
787
- if (synchronous) {
788
- // Lint files synchronously
789
- while (!done && (syncItem = files.shift())) {
796
+ if (done) {
797
+ // Abort for error or nothing left to do
798
+ } else if (files.length > 0) {
799
+ // Lint next file
800
+ concurrency++;
801
+ currentItem = files.shift();
790
802
  lintFile(
791
803
  ruleList,
792
- syncItem,
804
+ currentItem,
793
805
  md,
794
806
  config,
795
807
  frontMatter,
@@ -798,59 +810,48 @@ function lintInput(options, synchronous, callback) {
798
810
  resultVersion,
799
811
  fs,
800
812
  synchronous,
801
- syncCallback
813
+ lintWorkerCallback
802
814
  );
803
- }
804
- return done || callback(null, results);
805
- }
806
- // Lint files asynchronously
807
- let concurrency = 0;
808
- // eslint-disable-next-line jsdoc/require-jsdoc
809
- function lintConcurrently() {
810
- const asyncItem = files.shift();
811
- if (done) {
812
- // Nothing to do
813
- } else if (asyncItem) {
815
+ } else if (stringsKeys.length > 0) {
816
+ // Lint next string
814
817
  concurrency++;
815
- lintFile(
818
+ currentItem = stringsKeys.shift();
819
+ lintContent(
816
820
  ruleList,
817
- asyncItem,
821
+ currentItem,
822
+ strings[currentItem] || "",
818
823
  md,
819
824
  config,
820
825
  frontMatter,
821
826
  handleRuleFailures,
822
827
  noInlineConfig,
823
828
  resultVersion,
824
- fs,
825
- synchronous,
826
- (err, result) => {
827
- concurrency--;
828
- if (err) {
829
- done = true;
830
- return callback(err);
831
- }
832
- results[asyncItem] = result;
833
- lintConcurrently();
834
- return null;
835
- }
829
+ lintWorkerCallback
836
830
  );
837
831
  } else if (concurrency === 0) {
832
+ // Finish
838
833
  done = true;
839
834
  return callback(null, results);
840
835
  }
841
836
  return null;
842
837
  }
843
- // Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
844
- // delay suggests that a concurrency factor of 8 can eliminate the impact
845
- // of that delay (i.e., total time is the same as with no delay).
846
- lintConcurrently();
847
- lintConcurrently();
848
- lintConcurrently();
849
- lintConcurrently();
850
- lintConcurrently();
851
- lintConcurrently();
852
- lintConcurrently();
853
- lintConcurrently();
838
+ if (synchronous) {
839
+ while (!done) {
840
+ lintWorker();
841
+ }
842
+ } else {
843
+ // Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
844
+ // delay suggests that a concurrency factor of 8 can eliminate the impact
845
+ // of that delay (i.e., total time is the same as with no delay).
846
+ lintWorker();
847
+ lintWorker();
848
+ lintWorker();
849
+ lintWorker();
850
+ lintWorker();
851
+ lintWorker();
852
+ lintWorker();
853
+ lintWorker();
854
+ }
854
855
  return null;
855
856
  }
856
857
 
@@ -906,12 +907,13 @@ function parseConfiguration(name, content, parsers) {
906
907
  let config = null;
907
908
  let message = "";
908
909
  const errors = [];
910
+ let index = 0;
909
911
  // Try each parser
910
912
  (parsers || [ JSON.parse ]).every((parser) => {
911
913
  try {
912
914
  config = parser(content);
913
915
  } catch (error) {
914
- errors.push(error.message);
916
+ errors.push(`Parser ${index++}: ${error.message}`);
915
917
  }
916
918
  return !config;
917
919
  });
@@ -1102,7 +1104,7 @@ function readConfigSync(file, parsers, fs) {
1102
1104
  * @returns {string} SemVer string.
1103
1105
  */
1104
1106
  function getVersion() {
1105
- return require("../package.json").version;
1107
+ return require("./constants").version;
1106
1108
  }
1107
1109
 
1108
1110
  // Export a/synchronous/Promise APIs
@@ -1172,7 +1174,7 @@ module.exports = markdownlint;
1172
1174
  *
1173
1175
  * @typedef {Object} RuleOnErrorInfo
1174
1176
  * @property {number} lineNumber Line number (1-based).
1175
- * @property {string} [details] Details about the error.
1177
+ * @property {string} [detail] Detail about the error.
1176
1178
  * @property {string} [context] Context for the error.
1177
1179
  * @property {number[]} [range] Column number (1-based) and length.
1178
1180
  * @property {RuleOnErrorFixInfo} [fixInfo] Fix information.
@@ -1196,6 +1198,7 @@ module.exports = markdownlint;
1196
1198
  * @property {string} description Rule description.
1197
1199
  * @property {URL} [information] Link to more information.
1198
1200
  * @property {string[]} tags Rule tag(s).
1201
+ * @property {boolean} [asynchronous] True if asynchronous.
1199
1202
  * @property {RuleFunction} function Rule implementation.
1200
1203
  */
1201
1204
 
package/lib/md007.js CHANGED
@@ -13,12 +13,14 @@ module.exports = {
13
13
  "function": function MD007(params, onError) {
14
14
  const indent = Number(params.config.indent || 2);
15
15
  const startIndented = !!params.config.start_indented;
16
+ const startIndent = Number(params.config.start_indent || indent);
16
17
  flattenedLists().forEach((list) => {
17
18
  if (list.unordered && list.parentsUnordered) {
18
19
  list.items.forEach((item) => {
19
20
  const { lineNumber, line } = item;
20
- const expectedNesting = list.nesting + (startIndented ? 1 : 0);
21
- const expectedIndent = expectedNesting * indent;
21
+ const expectedIndent =
22
+ (startIndented ? startIndent : 0) +
23
+ (list.nesting * indent);
22
24
  const actualIndent = indentFor(item);
23
25
  let range = null;
24
26
  let editColumn = 1;