markdownlint 0.29.0 → 0.31.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.
@@ -4,9 +4,13 @@
4
4
 
5
5
  // @ts-ignore
6
6
  const {
7
- gfmAutolinkLiteral, gfmFootnote, gfmTable, parse, postprocess, preprocess
7
+ gfmAutolinkLiteral, gfmFootnote, gfmTable, math, parse, postprocess,
8
+ preprocess
8
9
  // @ts-ignore
9
10
  } = require("markdownlint-micromark");
11
+ const { newLineRe } = require("./shared.js");
12
+
13
+ const flatTokensSymbol = Symbol("flat-tokens");
10
14
 
11
15
  /**
12
16
  * Markdown token.
@@ -21,25 +25,58 @@ const {
21
25
  * @property {Token[]} children Child tokens.
22
26
  */
23
27
 
28
+ /**
29
+ * Returns whether a token is an htmlFlow type containing an HTML comment.
30
+ *
31
+ * @param {Token} token Micromark token.
32
+ * @returns {boolean} True iff token is htmlFlow containing a comment.
33
+ */
34
+ function isHtmlFlowComment(token) {
35
+ const { text, type } = token;
36
+ if (
37
+ (type === "htmlFlow") &&
38
+ text.startsWith("<!--") &&
39
+ text.endsWith("-->")
40
+ ) {
41
+ const comment = text.slice(4, -3);
42
+ return (
43
+ !comment.startsWith(">") &&
44
+ !comment.startsWith("->") &&
45
+ !comment.endsWith("-") &&
46
+ !comment.includes("--")
47
+ );
48
+ }
49
+ return false;
50
+ }
51
+
24
52
  /**
25
53
  * Parses a Markdown document and returns Micromark events.
26
54
  *
27
55
  * @param {string} markdown Markdown document.
28
- * @param {Object} [options] Options for micromark.
29
- * @param {boolean} [refsDefined] Whether to treat references as defined.
56
+ * @param {Object} [micromarkOptions] Options for micromark.
57
+ * @param {boolean} [referencesDefined] Treat references as defined.
30
58
  * @returns {Object[]} Micromark events.
31
59
  */
32
- function getMicromarkEvents(markdown, options = {}, refsDefined = true) {
60
+ function getMicromarkEvents(
61
+ markdown,
62
+ micromarkOptions = {},
63
+ referencesDefined = true
64
+ ) {
33
65
 
34
66
  // Customize options object to add useful extensions
35
- options.extensions = options.extensions || [];
36
- options.extensions.push(gfmAutolinkLiteral, gfmFootnote(), gfmTable);
67
+ micromarkOptions.extensions = micromarkOptions.extensions || [];
68
+ micromarkOptions.extensions.push(
69
+ gfmAutolinkLiteral(),
70
+ gfmFootnote(),
71
+ gfmTable(),
72
+ math()
73
+ );
37
74
 
38
75
  // Use micromark to parse document into Events
39
76
  const encoding = undefined;
40
77
  const eol = true;
41
- const parseContext = parse(options);
42
- if (refsDefined) {
78
+ const parseContext = parse(micromarkOptions);
79
+ if (referencesDefined) {
43
80
  // Customize ParseContext to treat all references as defined
44
81
  parseContext.defined.includes = (searchElement) => searchElement.length > 0;
45
82
  }
@@ -52,93 +89,171 @@ function getMicromarkEvents(markdown, options = {}, refsDefined = true) {
52
89
  * Parses a Markdown document and returns (frozen) tokens.
53
90
  *
54
91
  * @param {string} markdown Markdown document.
55
- * @param {Object} [options] Options for micromark.
56
- * @param {boolean} [refsDefined] Whether to treat references as defined.
92
+ * @param {Object} micromarkOptions Options for micromark.
93
+ * @param {boolean} referencesDefined Treat references as defined.
94
+ * @param {number} lineDelta Offset to apply to start/end line.
57
95
  * @returns {Token[]} Micromark tokens (frozen).
58
96
  */
59
- function micromarkParse(markdown, options = {}, refsDefined = true) {
60
-
97
+ function micromarkParseWithOffset(
98
+ markdown,
99
+ micromarkOptions,
100
+ referencesDefined,
101
+ lineDelta
102
+ ) {
61
103
  // Use micromark to parse document into Events
62
- const events =
63
- getMicromarkEvents(markdown, options, refsDefined);
104
+ const events = getMicromarkEvents(
105
+ markdown, micromarkOptions, referencesDefined
106
+ );
64
107
 
65
108
  // Create Token objects
66
109
  const document = [];
110
+ let flatTokens = [];
67
111
  let current = {
68
112
  "children": document
69
113
  };
70
114
  const history = [ current ];
115
+ let reparseOptions = null;
116
+ let lines = null;
117
+ let skipHtmlFlowChildren = false;
71
118
  for (const event of events) {
72
119
  const [ kind, token, context ] = event;
73
120
  const { type, start, end } = token;
74
121
  const { "column": startColumn, "line": startLine } = start;
75
122
  const { "column": endColumn, "line": endLine } = end;
76
123
  const text = context.sliceSerialize(token);
77
- if (kind === "enter") {
124
+ if ((kind === "enter") && !skipHtmlFlowChildren) {
78
125
  const previous = current;
79
126
  history.push(previous);
80
127
  current = {
81
128
  type,
82
- startLine,
129
+ "startLine": startLine + lineDelta,
83
130
  startColumn,
84
- endLine,
131
+ "endLine": endLine + lineDelta,
85
132
  endColumn,
86
133
  text,
87
134
  "children": []
88
135
  };
89
136
  previous.children.push(current);
90
- } else if (kind === "exit") {
91
- Object.freeze(current.children);
92
- Object.freeze(current);
137
+ flatTokens.push(current);
93
138
  // @ts-ignore
94
- current = history.pop();
139
+ if ((current.type === "htmlFlow") && !isHtmlFlowComment(current)) {
140
+ skipHtmlFlowChildren = true;
141
+ if (!reparseOptions || !lines) {
142
+ reparseOptions = {
143
+ ...micromarkOptions,
144
+ "extensions": [
145
+ {
146
+ "disable": {
147
+ "null": [ "codeIndented", "htmlFlow" ]
148
+ }
149
+ }
150
+ ]
151
+ };
152
+ lines = markdown.split(newLineRe);
153
+ }
154
+ const reparseMarkdown = lines
155
+ .slice(current.startLine - 1, current.endLine)
156
+ .join("\n");
157
+ const tokens = micromarkParseWithOffset(
158
+ reparseMarkdown,
159
+ reparseOptions,
160
+ referencesDefined,
161
+ current.startLine - 1
162
+ );
163
+ current.children = tokens;
164
+ // Avoid stack overflow of Array.push(...spread)
165
+ // eslint-disable-next-line unicorn/prefer-spread
166
+ flatTokens = flatTokens.concat(tokens[flatTokensSymbol]);
167
+ }
168
+ } else if (kind === "exit") {
169
+ if (type === "htmlFlow") {
170
+ skipHtmlFlowChildren = false;
171
+ }
172
+ if (!skipHtmlFlowChildren) {
173
+ Object.freeze(current.children);
174
+ Object.freeze(current);
175
+ // @ts-ignore
176
+ current = history.pop();
177
+ }
95
178
  }
96
179
  }
97
180
 
98
181
  // Return document
182
+ Object.defineProperty(document, flatTokensSymbol, { "value": flatTokens });
99
183
  Object.freeze(document);
100
184
  return document;
101
185
  }
102
186
 
103
- // /**
104
- // * Log the structure of a Micromark token list.
105
- // *
106
- // * @param {Token[]} tokens Micromark tokens.
107
- // * @param {number} depth Tree depth.
108
- // * @returns {void}
109
- // */
110
- // function consoleLogTokens(tokens, depth = 0) {
111
- // for (const token of tokens) {
112
- // const { children, text, type } = token;
113
- // console.log(
114
- // `${" ".repeat(depth * 2)}${type} ${text.replace(/\n/g, "\\n")}`
115
- // );
116
- // if (children.length > 0) {
117
- // consoleLogTokens(children, depth + 1);
118
- // }
119
- // }
120
- // }
187
+ /**
188
+ * Parses a Markdown document and returns (frozen) tokens.
189
+ *
190
+ * @param {string} markdown Markdown document.
191
+ * @param {Object} [micromarkOptions] Options for micromark.
192
+ * @param {boolean} [referencesDefined] Treat references as defined.
193
+ * @returns {Token[]} Micromark tokens (frozen).
194
+ */
195
+ function micromarkParse(
196
+ markdown,
197
+ micromarkOptions = {},
198
+ referencesDefined = true
199
+ ) {
200
+ return micromarkParseWithOffset(
201
+ markdown,
202
+ micromarkOptions,
203
+ referencesDefined,
204
+ 0
205
+ );
206
+ }
207
+
208
+ /**
209
+ * @callback AllowedPredicate
210
+ * @param {Token} token Micromark token.
211
+ * @returns {boolean} True iff allowed.
212
+ */
213
+
214
+ /**
215
+ * @callback TransformPredicate
216
+ * @param {Token} token Micromark token.
217
+ * @returns {Token[]} Child tokens.
218
+ */
121
219
 
122
220
  /**
123
221
  * Filter a list of Micromark tokens by predicate.
124
222
  *
125
223
  * @param {Token[]} tokens Micromark tokens.
126
- * @param {Function} allowed Allowed token predicate.
127
- * @param {Function} [transformChildren] Transform children predicate.
224
+ * @param {AllowedPredicate} allowed Allowed token predicate.
225
+ * @param {TransformPredicate} [transformChildren] Transform predicate.
128
226
  * @returns {Token[]} Filtered tokens.
129
227
  */
130
228
  function filterByPredicate(tokens, allowed, transformChildren) {
131
229
  const result = [];
132
- const pending = [ ...tokens ];
133
- let token = null;
134
- while ((token = pending.shift())) {
135
- if (allowed(token)) {
136
- result.push(token);
230
+ const queue = [
231
+ {
232
+ "array": tokens,
233
+ "index": 0
137
234
  }
138
- if (token.children.length > 0) {
139
- const transformed =
140
- transformChildren ? transformChildren(token) : token.children;
141
- pending.unshift(...transformed);
235
+ ];
236
+ while (queue.length > 0) {
237
+ const current = queue[queue.length - 1];
238
+ const { array, index } = current;
239
+ if (index < array.length) {
240
+ const token = array[current.index++];
241
+ if (allowed(token)) {
242
+ result.push(token);
243
+ }
244
+ const { children } = token;
245
+ if (children.length > 0) {
246
+ const transformed =
247
+ transformChildren ? transformChildren(token) : children;
248
+ queue.push(
249
+ {
250
+ "array": transformed,
251
+ "index": 0
252
+ }
253
+ );
254
+ }
255
+ } else {
256
+ queue.pop();
142
257
  }
143
258
  }
144
259
  return result;
@@ -152,10 +267,33 @@ function filterByPredicate(tokens, allowed, transformChildren) {
152
267
  * @returns {Token[]} Filtered tokens.
153
268
  */
154
269
  function filterByTypes(tokens, allowed) {
155
- return filterByPredicate(
156
- tokens,
157
- (token) => allowed.includes(token.type)
270
+ const predicate = (token) => allowed.includes(token.type);
271
+ const flatTokens = tokens[flatTokensSymbol];
272
+ if (flatTokens) {
273
+ return flatTokens.filter(predicate);
274
+ }
275
+ return filterByPredicate(tokens, predicate);
276
+ }
277
+
278
+ /**
279
+ * Gets the heading level of a Micromark heading tokan.
280
+ *
281
+ * @param {Token} heading Micromark heading token.
282
+ * @returns {number} Heading level.
283
+ */
284
+ function getHeadingLevel(heading) {
285
+ const headingSequence = filterByTypes(
286
+ heading.children,
287
+ [ "atxHeadingSequence", "setextHeadingLineSequence" ]
158
288
  );
289
+ let level = 1;
290
+ const { text } = headingSequence[0];
291
+ if (text[0] === "#") {
292
+ level = Math.min(text.length, 6);
293
+ } else if (text[0] === "-") {
294
+ level = 2;
295
+ }
296
+ return level;
159
297
  }
160
298
 
161
299
  /**
@@ -232,6 +370,7 @@ module.exports = {
232
370
  "parse": micromarkParse,
233
371
  filterByPredicate,
234
372
  filterByTypes,
373
+ getHeadingLevel,
235
374
  getHtmlTagInfo,
236
375
  getMicromarkEvents,
237
376
  getTokenTextByType,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint-rule-helpers",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "A collection of markdownlint helper functions for custom rules",
5
5
  "main": "./helpers.js",
6
6
  "exports": "./helpers.js",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "bugs": "https://github.com/DavidAnson/markdownlint/issues",
15
15
  "engines": {
16
- "node": ">=14.18.0"
16
+ "node": ">=16"
17
17
  },
18
18
  "dependencies": {
19
19
  "markdownlint-micromark": "0.1.2"
@@ -0,0 +1,7 @@
1
+ // @ts-check
2
+
3
+ "use strict";
4
+
5
+ // Regular expression for matching common newline characters
6
+ // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
7
+ module.exports.newLineRe = /\r\n?|\n/g;
package/lib/cache.js CHANGED
@@ -15,8 +15,6 @@ module.exports.codeBlockAndSpanRanges =
15
15
  () => map.get("codeBlockAndSpanRanges");
16
16
  module.exports.flattenedLists =
17
17
  () => map.get("flattenedLists");
18
- module.exports.htmlElementRanges =
19
- () => map.get("htmlElementRanges");
20
18
  module.exports.lineMetadata =
21
19
  () => map.get("lineMetadata");
22
20
  module.exports.referenceLinkImageData =
package/lib/constants.js CHANGED
@@ -11,4 +11,4 @@ module.exports.fixableRuleNames = [
11
11
  "MD044", "MD047", "MD049", "MD050", "MD051", "MD053"
12
12
  ];
13
13
  module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
14
- module.exports.version = "0.29.0";
14
+ module.exports.version = "0.31.0";
@@ -8,7 +8,7 @@ export = markdownlint;
8
8
  */
9
9
  declare function markdownlint(options: Options | null, callback: LintCallback): void;
10
10
  declare namespace markdownlint {
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, ResolveConfigExtendsCallback };
11
+ export { markdownlintSync as sync, readConfig, readConfigSync, getVersion, promises, RuleFunction, RuleParams, MarkdownItToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintContentCallback, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback, ResolveConfigExtendsCallback };
12
12
  }
13
13
  /**
14
14
  * Configuration options.
@@ -64,7 +64,7 @@ type Options = {
64
64
  /**
65
65
  * Called with the result of the lint function.
66
66
  */
67
- type LintCallback = (err: Error | null, results?: LintResults) => void;
67
+ type LintCallback = (error: Error | null, results?: LintResults) => void;
68
68
  /**
69
69
  * Lint specified Markdown files synchronously.
70
70
  *
@@ -218,6 +218,10 @@ type RuleOnErrorInfo = {
218
218
  * Context for the error.
219
219
  */
220
220
  context?: string;
221
+ /**
222
+ * Link to more information.
223
+ */
224
+ information?: URL;
221
225
  /**
222
226
  * Column number (1-based) and length.
223
227
  */
@@ -349,6 +353,10 @@ type FixInfo = {
349
353
  */
350
354
  insertText?: string;
351
355
  };
356
+ /**
357
+ * Called with the result of linting a string or document.
358
+ */
359
+ type LintContentCallback = (error: Error | null, result?: LintError[]) => void;
352
360
  /**
353
361
  * Configuration object for linting rules. For a detailed schema, see
354
362
  * {@link ../schema/markdownlint-config-schema.json}.
@@ -59,7 +59,7 @@ function validateRuleList(ruleList, synchronous) {
59
59
  if (
60
60
  !result &&
61
61
  rule.information &&
62
- (Object.getPrototypeOf(rule.information) !== URL.prototype)
62
+ !helpers.isUrl(rule.information)
63
63
  ) {
64
64
  result = newError("information");
65
65
  }
@@ -481,7 +481,7 @@ function getEnabledRulesPerLineNumber(
481
481
  applyEnableDisable(
482
482
  action,
483
483
  parameter,
484
- enabledRulesPerLineNumber[nextLineNumber] || {}
484
+ enabledRulesPerLineNumber[nextLineNumber]
485
485
  );
486
486
  }
487
487
  }
@@ -520,7 +520,7 @@ function getEnabledRulesPerLineNumber(
520
520
  * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
521
521
  * @param {boolean} noInlineConfig Whether to allow inline configuration.
522
522
  * @param {number} resultVersion Version of the LintResults object to return.
523
- * @param {Function} callback Callback (err, result) function.
523
+ * @param {LintContentCallback} callback Callback (err, result) function.
524
524
  * @returns {void}
525
525
  */
526
526
  function lintContent(
@@ -583,14 +583,11 @@ function lintContent(
583
583
  helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata);
584
584
  const flattenedLists =
585
585
  helpers.flattenLists(paramsBase.parsers.markdownit.tokens);
586
- const htmlElementRanges =
587
- helpers.htmlElementRanges(paramsBase, lineMetadata);
588
586
  const referenceLinkImageData =
589
587
  helpers.getReferenceLinkImageData(paramsBase);
590
588
  cache.set({
591
589
  codeBlockAndSpanRanges,
592
590
  flattenedLists,
593
- htmlElementRanges,
594
591
  lineMetadata,
595
592
  referenceLinkImageData
596
593
  });
@@ -629,6 +626,10 @@ function lintContent(
629
626
  !helpers.isString(errorInfo.context)) {
630
627
  throwError("context");
631
628
  }
629
+ if (errorInfo.information &&
630
+ !helpers.isUrl(errorInfo.information)) {
631
+ throwError("information");
632
+ }
632
633
  if (errorInfo.range &&
633
634
  (!Array.isArray(errorInfo.range) ||
634
635
  (errorInfo.range.length !== 2) ||
@@ -681,12 +682,13 @@ function lintContent(
681
682
  cleanFixInfo.insertText = fixInfo.insertText;
682
683
  }
683
684
  }
685
+ const information = errorInfo.information || rule.information;
684
686
  results.push({
685
687
  lineNumber,
686
688
  "ruleName": rule.names[0],
687
689
  "ruleNames": rule.names,
688
690
  "ruleDescription": rule.description,
689
- "ruleInformation": rule.information ? rule.information.href : null,
691
+ "ruleInformation": information ? information.href : null,
690
692
  "errorDetail": errorInfo.detail || null,
691
693
  "errorContext": errorInfo.context || null,
692
694
  "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
@@ -807,7 +809,7 @@ function lintContent(
807
809
  * @param {number} resultVersion Version of the LintResults object to return.
808
810
  * @param {Object} fs File system implementation.
809
811
  * @param {boolean} synchronous Whether to execute synchronously.
810
- * @param {Function} callback Callback (err, result) function.
812
+ * @param {LintContentCallback} callback Callback (err, result) function.
811
813
  * @returns {void}
812
814
  */
813
815
  function lintFile(
@@ -857,15 +859,26 @@ function lintFile(
857
859
  *
858
860
  * @param {Options | null} options Options object.
859
861
  * @param {boolean} synchronous Whether to execute synchronously.
860
- * @param {Function} callback Callback (err, result) function.
862
+ * @param {LintCallback} callback Callback (err, result) function.
861
863
  * @returns {void}
862
864
  */
863
865
  function lintInput(options, synchronous, callback) {
864
866
  // Normalize inputs
865
867
  options = options || {};
866
868
  callback = callback || function noop() {};
869
+ const customRuleList =
870
+ [ options.customRules || [] ]
871
+ .flat()
872
+ .map((rule) => ({
873
+ "names": helpers.cloneIfArray(rule.names),
874
+ "description": rule.description,
875
+ "information": helpers.cloneIfUrl(rule.information),
876
+ "tags": helpers.cloneIfArray(rule.tags),
877
+ "asynchronous": rule.asynchronous,
878
+ "function": rule.function
879
+ }));
867
880
  // eslint-disable-next-line unicorn/prefer-spread
868
- const ruleList = rules.concat(options.customRules || []);
881
+ const ruleList = rules.concat(customRuleList);
869
882
  const ruleErr = validateRuleList(ruleList, synchronous);
870
883
  if (ruleErr) {
871
884
  callback(ruleErr);
@@ -1009,7 +1022,7 @@ function markdownlintPromise(options) {
1009
1022
  * @returns {LintResults} Results object.
1010
1023
  */
1011
1024
  function markdownlintSync(options) {
1012
- let results = {};
1025
+ let results = null;
1013
1026
  lintInput(options, true, function callback(error, res) {
1014
1027
  if (error) {
1015
1028
  throw error;
@@ -1163,6 +1176,7 @@ function readConfig(file, parsers, fs, callback) {
1163
1176
  }
1164
1177
  // Read file
1165
1178
  file = helpers.expandTildePath(file, require("node:os"));
1179
+ // eslint-disable-next-line n/prefer-promises/fs
1166
1180
  fs.readFile(file, "utf8", (err, content) => {
1167
1181
  if (err) {
1168
1182
  // @ts-ignore
@@ -1314,6 +1328,7 @@ module.exports = markdownlint;
1314
1328
  * @property {number} lineNumber Line number (1-based).
1315
1329
  * @property {string} [detail] Detail about the error.
1316
1330
  * @property {string} [context] Context for the error.
1331
+ * @property {URL} [information] Link to more information.
1317
1332
  * @property {number[]} [range] Column number (1-based) and length.
1318
1333
  * @property {RuleOnErrorFixInfo} [fixInfo] Fix information.
1319
1334
  */
@@ -1402,12 +1417,21 @@ module.exports = markdownlint;
1402
1417
  * @property {string} [insertText] Text to insert (after deleting).
1403
1418
  */
1404
1419
 
1420
+ /**
1421
+ * Called with the result of linting a string or document.
1422
+ *
1423
+ * @callback LintContentCallback
1424
+ * @param {Error | null} error Error iff failed.
1425
+ * @param {LintError[]} [result] Result iff successful.
1426
+ * @returns {void}
1427
+ */
1428
+
1405
1429
  /**
1406
1430
  * Called with the result of the lint function.
1407
1431
  *
1408
1432
  * @callback LintCallback
1409
- * @param {Error | null} err Error object or null.
1410
- * @param {LintResults} [results] Lint results.
1433
+ * @param {Error | null} error Error object iff failed.
1434
+ * @param {LintResults} [results] Lint results iff succeeded.
1411
1435
  * @returns {void}
1412
1436
  */
1413
1437