eslint-plugin-markdown-preferences 0.4.0 → 0.6.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.
- package/README.md +2 -1
- package/lib/index.d.ts +74 -3
- package/lib/index.js +259 -37
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -92,8 +92,9 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
92
92
|
|:--------|:------------|:-------:|:-----------:|
|
|
93
93
|
| [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | 🔧 | ⭐ |
|
|
94
94
|
| [markdown-preferences/no-text-backslash-linebreak](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html) | disallow text backslash at the end of a line. | | ⭐ |
|
|
95
|
-
| [markdown-preferences/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html) | trailing whitespace at the end of lines in Markdown files. | 🔧 | |
|
|
95
|
+
| [markdown-preferences/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html) | disallow trailing whitespace at the end of lines in Markdown files. | 🔧 | |
|
|
96
96
|
| [markdown-preferences/prefer-inline-code-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html) | enforce the use of inline code for specific words. | 🔧 | |
|
|
97
|
+
| [markdown-preferences/prefer-link-reference-definitions](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-link-reference-definitions.html) | enforce using link reference definitions instead of inline links | 🔧 | |
|
|
97
98
|
| [markdown-preferences/prefer-linked-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html) | enforce the specified word to be a link. | 🔧 | |
|
|
98
99
|
|
|
99
100
|
<!--RULES_TABLE_END-->
|
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,78 @@ import * as _eslint_core0 from "@eslint/core";
|
|
|
3
3
|
import { RuleDefinition } from "@eslint/core";
|
|
4
4
|
import { ESLint, Linter } from "eslint";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/rule-types.d.ts
|
|
7
|
+
declare module 'eslint' {
|
|
8
|
+
namespace Linter {
|
|
9
|
+
interface RulesRecord extends RuleOptions {}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
interface RuleOptions {
|
|
13
|
+
/**
|
|
14
|
+
* enforce consistent hard linebreak style.
|
|
15
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html
|
|
16
|
+
*/
|
|
17
|
+
'markdown-preferences/hard-linebreak-style'?: Linter.RuleEntry<MarkdownPreferencesHardLinebreakStyle>;
|
|
18
|
+
/**
|
|
19
|
+
* disallow text backslash at the end of a line.
|
|
20
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html
|
|
21
|
+
*/
|
|
22
|
+
'markdown-preferences/no-text-backslash-linebreak'?: Linter.RuleEntry<[]>;
|
|
23
|
+
/**
|
|
24
|
+
* disallow trailing whitespace at the end of lines in Markdown files.
|
|
25
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html
|
|
26
|
+
*/
|
|
27
|
+
'markdown-preferences/no-trailing-spaces'?: Linter.RuleEntry<MarkdownPreferencesNoTrailingSpaces>;
|
|
28
|
+
/**
|
|
29
|
+
* enforce the use of inline code for specific words.
|
|
30
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html
|
|
31
|
+
*/
|
|
32
|
+
'markdown-preferences/prefer-inline-code-words'?: Linter.RuleEntry<MarkdownPreferencesPreferInlineCodeWords>;
|
|
33
|
+
/**
|
|
34
|
+
* enforce using link reference definitions instead of inline links
|
|
35
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-link-reference-definitions.html
|
|
36
|
+
*/
|
|
37
|
+
'markdown-preferences/prefer-link-reference-definitions'?: Linter.RuleEntry<MarkdownPreferencesPreferLinkReferenceDefinitions>;
|
|
38
|
+
/**
|
|
39
|
+
* enforce the specified word to be a link.
|
|
40
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html
|
|
41
|
+
*/
|
|
42
|
+
'markdown-preferences/prefer-linked-words'?: Linter.RuleEntry<MarkdownPreferencesPreferLinkedWords>;
|
|
43
|
+
}
|
|
44
|
+
type MarkdownPreferencesHardLinebreakStyle = [] | [{
|
|
45
|
+
style?: ("backslash" | "spaces");
|
|
46
|
+
}];
|
|
47
|
+
type MarkdownPreferencesNoTrailingSpaces = [] | [{
|
|
48
|
+
skipBlankLines?: boolean;
|
|
49
|
+
ignoreComments?: boolean;
|
|
50
|
+
}];
|
|
51
|
+
type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
52
|
+
words: string[];
|
|
53
|
+
ignores?: {
|
|
54
|
+
words?: (string | string[]);
|
|
55
|
+
node?: {
|
|
56
|
+
[k: string]: unknown | undefined;
|
|
57
|
+
};
|
|
58
|
+
[k: string]: unknown | undefined;
|
|
59
|
+
}[];
|
|
60
|
+
[k: string]: unknown | undefined;
|
|
61
|
+
}];
|
|
62
|
+
type MarkdownPreferencesPreferLinkReferenceDefinitions = [] | [{
|
|
63
|
+
minLinks?: number;
|
|
64
|
+
}];
|
|
65
|
+
type MarkdownPreferencesPreferLinkedWords = [] | [{
|
|
66
|
+
words: ({
|
|
67
|
+
[k: string]: (string | null);
|
|
68
|
+
} | string[]);
|
|
69
|
+
ignores?: {
|
|
70
|
+
words?: (string | string[]);
|
|
71
|
+
node?: {
|
|
72
|
+
[k: string]: unknown | undefined;
|
|
73
|
+
};
|
|
74
|
+
[k: string]: unknown | undefined;
|
|
75
|
+
}[];
|
|
76
|
+
[k: string]: unknown | undefined;
|
|
77
|
+
}];
|
|
7
78
|
declare namespace recommended_d_exports {
|
|
8
79
|
export { files, language, name$1 as name, plugins, rules$1 as rules };
|
|
9
80
|
}
|
|
@@ -19,7 +90,7 @@ declare namespace meta_d_exports {
|
|
|
19
90
|
export { name, version };
|
|
20
91
|
}
|
|
21
92
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
22
|
-
declare const version: "0.
|
|
93
|
+
declare const version: "0.6.0";
|
|
23
94
|
//#endregion
|
|
24
95
|
//#region src/index.d.ts
|
|
25
96
|
declare const configs: {
|
|
@@ -34,4 +105,4 @@ declare const _default: {
|
|
|
34
105
|
rules: Record<string, RuleDefinition<_eslint_core0.RuleDefinitionTypeOptions>>;
|
|
35
106
|
};
|
|
36
107
|
//#endregion
|
|
37
|
-
export { configs, _default as default, meta_d_exports as meta, rules };
|
|
108
|
+
export { RuleOptions, configs, _default as default, meta_d_exports as meta, rules };
|
package/lib/index.js
CHANGED
|
@@ -131,7 +131,7 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
131
131
|
meta: {
|
|
132
132
|
type: "layout",
|
|
133
133
|
docs: {
|
|
134
|
-
description: "trailing whitespace at the end of lines in Markdown files.",
|
|
134
|
+
description: "disallow trailing whitespace at the end of lines in Markdown files.",
|
|
135
135
|
categories: []
|
|
136
136
|
},
|
|
137
137
|
fixable: "whitespace",
|
|
@@ -245,13 +245,14 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
245
245
|
|
|
246
246
|
//#endregion
|
|
247
247
|
//#region src/utils/search-words.ts
|
|
248
|
-
const RE_BOUNDARY = /^[\s\p{Letter_Number}\p{Modifier_Letter}\p{Modifier_Symbol}\p{Nonspacing_Mark}\p{Other_Letter}\p{Other_Symbol}\p{Script=Han}!"#$%&'()
|
|
248
|
+
const RE_BOUNDARY = /^[\s\p{Letter_Number}\p{Modifier_Letter}\p{Modifier_Symbol}\p{Nonspacing_Mark}\p{Other_Letter}\p{Other_Symbol}\p{Script=Han}!"#$%&'()*+,./:;<=>?\\{|}~\u{2ffc}-\u{303d}\u{30a0}-\u{30fb}\u{3192}-\u{32bf}\u{fe10}-\u{fe1f}\u{fe30}-\u{fe6f}\u{ff00}-\u{ffef}\u{2ebf0}-\u{2ee5d}]*$/u;
|
|
249
249
|
/**
|
|
250
250
|
* Iterate through words in a text node that match the specified words.
|
|
251
251
|
*/
|
|
252
|
-
function* iterateSearchWords(sourceCode, node, words) {
|
|
252
|
+
function* iterateSearchWords({ sourceCode, node, words, ignores }) {
|
|
253
253
|
const text = sourceCode.getText(node);
|
|
254
254
|
for (const word of words) {
|
|
255
|
+
if (ignores.ignore(word)) continue;
|
|
255
256
|
let startPosition = 0;
|
|
256
257
|
while (true) {
|
|
257
258
|
const index = text.indexOf(word, startPosition);
|
|
@@ -280,6 +281,57 @@ function* iterateSearchWords(sourceCode, node, words) {
|
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
}
|
|
284
|
+
const IGNORES_SCHEMA = {
|
|
285
|
+
type: "array",
|
|
286
|
+
items: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
words: { anyOf: [{ type: "string" }, {
|
|
290
|
+
type: "array",
|
|
291
|
+
items: { type: "string" }
|
|
292
|
+
}] },
|
|
293
|
+
node: { type: "object" }
|
|
294
|
+
},
|
|
295
|
+
additionalProperties: true
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Create a context for ignoring specific words or nodes.
|
|
300
|
+
*/
|
|
301
|
+
function createSearchWordsIgnoreContext(ignores) {
|
|
302
|
+
if (!ignores || ignores.length === 0) return {
|
|
303
|
+
enter: () => void 0,
|
|
304
|
+
exit: () => void 0,
|
|
305
|
+
ignore: () => false
|
|
306
|
+
};
|
|
307
|
+
const conditions = ignores.map((ignore) => {
|
|
308
|
+
const isIgnoreWord = ignore.words == null ? () => true : Array.isArray(ignore.words) ? (word) => ignore.words.includes(word) : (word) => ignore.words === word;
|
|
309
|
+
const node = ignore.node || {};
|
|
310
|
+
const keys = Object.keys(node);
|
|
311
|
+
return {
|
|
312
|
+
isIgnoreWord,
|
|
313
|
+
isIgnoreNode: (nodeToCheck) => {
|
|
314
|
+
return keys.every((key) => nodeToCheck[key] === node[key]);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
const currentIgnores = /* @__PURE__ */ new Set();
|
|
319
|
+
return {
|
|
320
|
+
enter(node) {
|
|
321
|
+
for (const ignore of conditions) if (ignore.isIgnoreNode(node)) currentIgnores.add({
|
|
322
|
+
node,
|
|
323
|
+
condition: ignore
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
exit(node) {
|
|
327
|
+
for (const element of [...currentIgnores]) if (element.node === node) currentIgnores.delete(element);
|
|
328
|
+
},
|
|
329
|
+
ignore(word) {
|
|
330
|
+
for (const { condition } of currentIgnores) if (condition.isIgnoreWord(word)) return true;
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
283
335
|
|
|
284
336
|
//#endregion
|
|
285
337
|
//#region src/rules/prefer-inline-code-words.ts
|
|
@@ -294,19 +346,30 @@ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
|
|
|
294
346
|
hasSuggestions: false,
|
|
295
347
|
schema: [{
|
|
296
348
|
type: "object",
|
|
297
|
-
properties: {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
349
|
+
properties: {
|
|
350
|
+
words: {
|
|
351
|
+
type: "array",
|
|
352
|
+
items: { type: "string" }
|
|
353
|
+
},
|
|
354
|
+
ignores: IGNORES_SCHEMA
|
|
355
|
+
},
|
|
356
|
+
required: ["words"],
|
|
357
|
+
additionalProperties: true
|
|
302
358
|
}],
|
|
303
359
|
messages: { requireInlineCode: "The word \"{{name}}\" should be in inline code." }
|
|
304
360
|
},
|
|
305
361
|
create(context) {
|
|
306
362
|
const sourceCode = context.sourceCode;
|
|
307
363
|
const words = context.options[0]?.words || [];
|
|
364
|
+
const ignores = createSearchWordsIgnoreContext(context.options[0]?.ignores);
|
|
308
365
|
let shortcutLinkReference = null;
|
|
309
366
|
return {
|
|
367
|
+
"*"(node) {
|
|
368
|
+
ignores.enter(node);
|
|
369
|
+
},
|
|
370
|
+
"*:exit"(node) {
|
|
371
|
+
ignores.exit(node);
|
|
372
|
+
},
|
|
310
373
|
linkReference(node) {
|
|
311
374
|
if (node.referenceType !== "shortcut") return;
|
|
312
375
|
if (shortcutLinkReference) return;
|
|
@@ -316,7 +379,12 @@ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
|
|
|
316
379
|
if (shortcutLinkReference === node) shortcutLinkReference = null;
|
|
317
380
|
},
|
|
318
381
|
text(node) {
|
|
319
|
-
for (const { word, loc, range } of iterateSearchWords(
|
|
382
|
+
for (const { word, loc, range } of iterateSearchWords({
|
|
383
|
+
sourceCode,
|
|
384
|
+
node,
|
|
385
|
+
words,
|
|
386
|
+
ignores
|
|
387
|
+
})) {
|
|
320
388
|
const shortcutLinkReferenceToReport = shortcutLinkReference;
|
|
321
389
|
context.report({
|
|
322
390
|
node,
|
|
@@ -335,6 +403,140 @@ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
|
|
|
335
403
|
}
|
|
336
404
|
});
|
|
337
405
|
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/rules/prefer-link-reference-definitions.ts
|
|
408
|
+
var prefer_link_reference_definitions_default = createRule("prefer-link-reference-definitions", {
|
|
409
|
+
meta: {
|
|
410
|
+
type: "layout",
|
|
411
|
+
docs: {
|
|
412
|
+
description: "enforce using link reference definitions instead of inline links",
|
|
413
|
+
categories: []
|
|
414
|
+
},
|
|
415
|
+
fixable: "code",
|
|
416
|
+
hasSuggestions: false,
|
|
417
|
+
schema: [{
|
|
418
|
+
type: "object",
|
|
419
|
+
properties: { minLinks: {
|
|
420
|
+
type: "number",
|
|
421
|
+
description: "minimum number of links to trigger the rule (default: 2)",
|
|
422
|
+
default: 2,
|
|
423
|
+
minimum: 1
|
|
424
|
+
} },
|
|
425
|
+
additionalProperties: false
|
|
426
|
+
}],
|
|
427
|
+
messages: { useLinkReferenceDefinitions: "Use link reference definitions instead of inline links." }
|
|
428
|
+
},
|
|
429
|
+
create(context) {
|
|
430
|
+
const sourceCode = context.sourceCode;
|
|
431
|
+
const options = context.options[0] || {};
|
|
432
|
+
const minLinks = options.minLinks ?? 2;
|
|
433
|
+
const definitions = [];
|
|
434
|
+
const links = [];
|
|
435
|
+
const linkReferences = [];
|
|
436
|
+
/**
|
|
437
|
+
* Verify links.
|
|
438
|
+
*/
|
|
439
|
+
function verify() {
|
|
440
|
+
const resourceToNodes = /* @__PURE__ */ new Map();
|
|
441
|
+
for (const link of links) getResourceNodes(link).links.push(link);
|
|
442
|
+
for (const linkReference of linkReferences) {
|
|
443
|
+
const definition = definitions.find((def) => def.identifier === linkReference.identifier);
|
|
444
|
+
if (definition) getResourceNodes(definition).linkReferences.push(linkReference);
|
|
445
|
+
}
|
|
446
|
+
for (const definition of definitions) getResourceNodes(definition).definitions.push(definition);
|
|
447
|
+
for (const map of resourceToNodes.values()) for (const nodes of map.values()) {
|
|
448
|
+
if (nodes.links.length === 0 || nodes.links.length + nodes.linkReferences.length < minLinks) continue;
|
|
449
|
+
for (const link of nodes.links) {
|
|
450
|
+
const linkInfo = getLinkInfo(link);
|
|
451
|
+
if (linkInfo.label === "") continue;
|
|
452
|
+
context.report({
|
|
453
|
+
node: link,
|
|
454
|
+
messageId: "useLinkReferenceDefinitions",
|
|
455
|
+
*fix(fixer) {
|
|
456
|
+
const definition = nodes.definitions[0];
|
|
457
|
+
let identifier;
|
|
458
|
+
if (definition) identifier = definition.label ?? definition.identifier;
|
|
459
|
+
else identifier = linkInfo.label.replaceAll(/\]/g, "-");
|
|
460
|
+
yield fixer.replaceText(link, `${sourceCode.text.slice(...linkInfo.labelRange)}${identifier === linkInfo.label ? "" : `[${identifier}]`}`);
|
|
461
|
+
if (!definition) {
|
|
462
|
+
const linkRange = sourceCode.getRange(link);
|
|
463
|
+
const reLineBreaks = /\n#{1,6}\s/gu;
|
|
464
|
+
reLineBreaks.lastIndex = linkRange[1];
|
|
465
|
+
const nextSectionMatch = reLineBreaks.exec(sourceCode.text);
|
|
466
|
+
const insertIndex = !nextSectionMatch ? sourceCode.text.trimEnd().length : nextSectionMatch.index;
|
|
467
|
+
yield fixer.insertTextAfterRange([insertIndex, insertIndex], `${sourceCode.text[insertIndex - 1] === "\n" ? "" : "\n"}\n[${identifier}]: ${sourceCode.text.slice(linkInfo.urlAndTitleRange[0] + 1, linkInfo.urlAndTitleRange[1] - 1).trim()}${nextSectionMatch ? "\n" : ""}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Get the resource nodes for a link or definition.
|
|
475
|
+
*/
|
|
476
|
+
function getResourceNodes(resource) {
|
|
477
|
+
const url = resource.url;
|
|
478
|
+
const title = resource.title ?? null;
|
|
479
|
+
let map = resourceToNodes.get(url);
|
|
480
|
+
if (!map) {
|
|
481
|
+
map = /* @__PURE__ */ new Map();
|
|
482
|
+
resourceToNodes.set(url, map);
|
|
483
|
+
}
|
|
484
|
+
let nodes = map.get(title);
|
|
485
|
+
if (!nodes) {
|
|
486
|
+
nodes = {
|
|
487
|
+
links: [],
|
|
488
|
+
linkReferences: [],
|
|
489
|
+
definitions: []
|
|
490
|
+
};
|
|
491
|
+
map.set(title, nodes);
|
|
492
|
+
}
|
|
493
|
+
return nodes;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
link(node) {
|
|
498
|
+
links.push(node);
|
|
499
|
+
},
|
|
500
|
+
linkReference(node) {
|
|
501
|
+
linkReferences.push(node);
|
|
502
|
+
},
|
|
503
|
+
definition(node) {
|
|
504
|
+
definitions.push(node);
|
|
505
|
+
},
|
|
506
|
+
"root:exit"() {
|
|
507
|
+
verify();
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
/**
|
|
511
|
+
* Get the range of the link label.
|
|
512
|
+
*/
|
|
513
|
+
function getLinkInfo(link) {
|
|
514
|
+
const range = sourceCode.getRange(link);
|
|
515
|
+
const linkLabelRange = getLinkLabelRange();
|
|
516
|
+
const linkLabelWithBracketsText = sourceCode.text.slice(...linkLabelRange);
|
|
517
|
+
const linkLabelText = linkLabelWithBracketsText.slice(1, -1).trim();
|
|
518
|
+
const urlStartIndex = sourceCode.text.indexOf("(", linkLabelRange[1]);
|
|
519
|
+
return {
|
|
520
|
+
label: linkLabelText,
|
|
521
|
+
labelRange: linkLabelRange,
|
|
522
|
+
urlAndTitleRange: [urlStartIndex, range[1]]
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Get the range of the link label.
|
|
526
|
+
*/
|
|
527
|
+
function getLinkLabelRange() {
|
|
528
|
+
if (link.children.length === 0) {
|
|
529
|
+
const index$1 = sourceCode.text.indexOf("]", range[0] + 1);
|
|
530
|
+
return [range[0], index$1 + 1];
|
|
531
|
+
}
|
|
532
|
+
const lastRange = sourceCode.getRange(link.children[link.children.length - 1]);
|
|
533
|
+
const index = sourceCode.text.indexOf("]", lastRange[0] + 1);
|
|
534
|
+
return [range[0], index + 1];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
338
540
|
//#endregion
|
|
339
541
|
//#region src/rules/prefer-linked-words.ts
|
|
340
542
|
var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
@@ -348,20 +550,25 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
348
550
|
hasSuggestions: false,
|
|
349
551
|
schema: [{
|
|
350
552
|
type: "object",
|
|
351
|
-
properties: {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
553
|
+
properties: {
|
|
554
|
+
words: { anyOf: [{
|
|
555
|
+
type: "object",
|
|
556
|
+
patternProperties: { "^[\\s\\S]+$": { type: ["string", "null"] } }
|
|
557
|
+
}, {
|
|
558
|
+
type: "array",
|
|
559
|
+
items: { type: "string" }
|
|
560
|
+
}] },
|
|
561
|
+
ignores: IGNORES_SCHEMA
|
|
562
|
+
},
|
|
563
|
+
required: ["words"],
|
|
564
|
+
additionalProperties: true
|
|
359
565
|
}],
|
|
360
566
|
messages: { requireLink: "The word \"{{name}}\" should be a link." }
|
|
361
567
|
},
|
|
362
568
|
create(context) {
|
|
363
569
|
const sourceCode = context.sourceCode;
|
|
364
570
|
const wordsOption = context.options[0]?.words || {};
|
|
571
|
+
const ignores = createSearchWordsIgnoreContext(context.options[0]?.ignores);
|
|
365
572
|
const links = Object.create(null);
|
|
366
573
|
const words = [];
|
|
367
574
|
if (Array.isArray(wordsOption)) words.push(...wordsOption);
|
|
@@ -373,18 +580,29 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
373
580
|
}
|
|
374
581
|
words.push(word);
|
|
375
582
|
}
|
|
376
|
-
let
|
|
583
|
+
let linkedNode = null;
|
|
377
584
|
return {
|
|
378
|
-
"
|
|
379
|
-
|
|
380
|
-
ignore = node;
|
|
585
|
+
"*"(node) {
|
|
586
|
+
ignores.enter(node);
|
|
381
587
|
},
|
|
382
|
-
"
|
|
383
|
-
|
|
588
|
+
"*:exit"(node) {
|
|
589
|
+
ignores.exit(node);
|
|
590
|
+
},
|
|
591
|
+
"link, linkReference"(node) {
|
|
592
|
+
if (linkedNode) return;
|
|
593
|
+
linkedNode = node;
|
|
594
|
+
},
|
|
595
|
+
"link, linkReference:exit"(node) {
|
|
596
|
+
if (linkedNode === node) linkedNode = null;
|
|
384
597
|
},
|
|
385
598
|
text(node) {
|
|
386
|
-
if (
|
|
387
|
-
for (const { word, loc, range } of iterateSearchWords(
|
|
599
|
+
if (linkedNode) return;
|
|
600
|
+
for (const { word, loc, range } of iterateSearchWords({
|
|
601
|
+
sourceCode,
|
|
602
|
+
node,
|
|
603
|
+
words,
|
|
604
|
+
ignores
|
|
605
|
+
})) {
|
|
388
606
|
const link = links[word];
|
|
389
607
|
context.report({
|
|
390
608
|
node,
|
|
@@ -398,17 +616,20 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
398
616
|
}
|
|
399
617
|
},
|
|
400
618
|
inlineCode(node) {
|
|
401
|
-
if (
|
|
402
|
-
for (const word of words)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
619
|
+
if (linkedNode) return;
|
|
620
|
+
for (const word of words) {
|
|
621
|
+
if (ignores.ignore(word)) continue;
|
|
622
|
+
if (node.value === word) {
|
|
623
|
+
const link = links[word];
|
|
624
|
+
context.report({
|
|
625
|
+
node,
|
|
626
|
+
messageId: "requireLink",
|
|
627
|
+
data: { name: word },
|
|
628
|
+
fix: link ? (fixer) => {
|
|
629
|
+
return fixer.replaceText(node, `[\`${word}\`](${link})`);
|
|
630
|
+
} : null
|
|
631
|
+
});
|
|
632
|
+
}
|
|
412
633
|
}
|
|
413
634
|
}
|
|
414
635
|
};
|
|
@@ -431,6 +652,7 @@ const rules$1 = [
|
|
|
431
652
|
no_text_backslash_linebreak_default,
|
|
432
653
|
no_trailing_spaces_default,
|
|
433
654
|
prefer_inline_code_words_default,
|
|
655
|
+
prefer_link_reference_definitions_default,
|
|
434
656
|
prefer_linked_words_default
|
|
435
657
|
];
|
|
436
658
|
|
|
@@ -466,7 +688,7 @@ __export(meta_exports, {
|
|
|
466
688
|
version: () => version
|
|
467
689
|
});
|
|
468
690
|
const name = "eslint-plugin-markdown-preferences";
|
|
469
|
-
const version = "0.
|
|
691
|
+
const version = "0.6.0";
|
|
470
692
|
|
|
471
693
|
//#endregion
|
|
472
694
|
//#region src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-markdown-preferences",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "ESLint plugin that enforces our markdown preferences",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"@types/eslint-utils": "^3.0.5",
|
|
72
72
|
"@types/estree": "^1.0.6",
|
|
73
73
|
"@types/json-schema": "^7.0.15",
|
|
74
|
+
"@types/mdast": "^4.0.4",
|
|
74
75
|
"@types/mocha": "^10.0.10",
|
|
75
76
|
"@types/node": "^22.13.10",
|
|
76
77
|
"@types/semver": "^7.5.8",
|