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.
- package/CONTRIBUTING.md +2 -0
- package/README.md +126 -83
- package/demo/markdownlint-browser.js +1743 -1288
- package/doc/Rules.md +145 -16
- package/helpers/helpers.js +429 -192
- package/helpers/package.json +6 -2
- package/lib/cache.js +15 -25
- package/lib/constants.js +1 -1
- package/lib/markdownlint.d.ts +21 -17
- package/lib/markdownlint.js +237 -151
- package/lib/md004.js +4 -4
- package/lib/md005.js +4 -4
- package/lib/md006.js +4 -4
- package/lib/md007.js +4 -4
- package/lib/md009.js +20 -13
- package/lib/md010.js +15 -2
- package/lib/md011.js +2 -2
- package/lib/md013.js +4 -5
- package/lib/md014.js +4 -4
- package/lib/md021.js +3 -1
- package/lib/md026.js +1 -1
- package/lib/md027.js +2 -2
- package/lib/md028.js +2 -2
- package/lib/md029.js +5 -4
- package/lib/md030.js +4 -4
- package/lib/md031.js +2 -2
- package/lib/md032.js +7 -7
- package/lib/md033.js +3 -4
- package/lib/md034.js +2 -2
- package/lib/md035.js +7 -2
- package/lib/md036.js +2 -2
- package/lib/md037.js +23 -20
- package/lib/md038.js +7 -4
- package/lib/md039.js +6 -4
- package/lib/md042.js +17 -10
- package/lib/md043.js +2 -2
- package/lib/md044.js +20 -9
- package/lib/md046.js +14 -13
- package/lib/md048.js +13 -14
- package/lib/md049-md050.js +73 -0
- package/lib/md051.js +87 -0
- package/lib/md052.js +35 -0
- package/lib/md053.js +59 -0
- package/lib/rules.js +6 -4
- package/package.json +25 -18
- package/schema/.markdownlint.jsonc +19 -1
- package/schema/.markdownlint.yaml +18 -1
- package/schema/markdownlint-config-schema.json +126 -728
- package/lib/md049.js +0 -45
- package/lib/md050.js +0 -45
package/lib/markdownlint.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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" ]
|
|
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
|
-
]
|
|
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
|
|
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
|
|
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
|
-
|
|
114
|
+
for (const file of keys) {
|
|
115
115
|
const fileResults = lintResults[file];
|
|
116
116
|
if (Array.isArray(fileResults)) {
|
|
117
|
-
|
|
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
|
-
|
|
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.
|
|
141
|
+
for (const [ ruleName, ruleResults ] of Object.entries(fileResults)) {
|
|
142
142
|
const rule = ruleNameToRule[ruleName.toUpperCase()];
|
|
143
|
-
const ruleResults
|
|
144
|
-
|
|
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
|
-
*
|
|
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
|
|
224
|
+
function annotateAndFreezeTokens(tokens, lines) {
|
|
198
225
|
let trMap = null;
|
|
199
|
-
|
|
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
|
-
|
|
223
|
-
|
|
244
|
+
}
|
|
245
|
+
// Annotate children with lineNumber
|
|
246
|
+
if (token.children) {
|
|
224
247
|
const codeSpanExtraLines = [];
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
284
|
+
for (const name of rule.names) {
|
|
260
285
|
const nameUpper = name.toUpperCase();
|
|
261
286
|
aliasToRuleNames[nameUpper] = [ ruleName ];
|
|
262
|
-
}
|
|
263
|
-
rule.tags
|
|
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
|
-
|
|
320
|
+
for (const rule of ruleList) {
|
|
296
321
|
const ruleName = rule.names[0].toUpperCase();
|
|
297
322
|
effectiveConfig[ruleName] = ruleDefault;
|
|
298
|
-
}
|
|
299
|
-
|
|
323
|
+
}
|
|
324
|
+
for (const ruleName of deprecatedRuleNames) {
|
|
300
325
|
effectiveConfig[ruleName] = false;
|
|
301
|
-
}
|
|
302
|
-
Object.keys(config)
|
|
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] || [])
|
|
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.
|
|
406
|
+
for (const [ lineIndex, line ] of input.entries()) {
|
|
347
407
|
if (!noInlineConfig) {
|
|
348
408
|
let match = null;
|
|
349
|
-
while ((match = helpers.
|
|
350
|
-
const action =
|
|
351
|
-
const
|
|
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
|
-
|
|
364
|
-
|
|
428
|
+
const { "config": parsed } = parseConfiguration(
|
|
429
|
+
"CONFIGURE-FILE", parameter, configParsers
|
|
430
|
+
);
|
|
431
|
+
if (parsed) {
|
|
365
432
|
config = {
|
|
366
433
|
...config,
|
|
367
|
-
...
|
|
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
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
545
|
+
content.split(helpers.newLineRe),
|
|
482
546
|
frontMatterLines,
|
|
483
547
|
noInlineConfig,
|
|
484
548
|
config,
|
|
485
|
-
|
|
549
|
+
configParsers,
|
|
550
|
+
mapAliasToRuleNames(ruleList)
|
|
486
551
|
);
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
helpers.codeBlockAndSpanRanges(
|
|
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
|
|
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(
|
|
727
|
-
|
|
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
|
-
|
|
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
|
-
|
|
870
|
+
3 : options.resultVersion;
|
|
770
871
|
const md = markdownIt({ "html": true });
|
|
771
872
|
const markdownItPlugins = options.markdownItPlugins || [];
|
|
772
|
-
|
|
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.
|
|
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 =
|
|
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}
|
|
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(
|
|
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 {
|
|
1218
|
-
* @property {Object} [fs] File system implementation.
|
|
1304
|
+
* @property {Object.<string, string>} [strings] Strings to lint.
|
|
1219
1305
|
*/
|
|
1220
1306
|
|
|
1221
1307
|
/**
|