eslint-plugin-markdown-preferences 0.5.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 +1 -0
- package/lib/index.d.ts +9 -1
- package/lib/index.js +137 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -94,6 +94,7 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
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
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
|
@@ -30,6 +30,11 @@ interface RuleOptions {
|
|
|
30
30
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html
|
|
31
31
|
*/
|
|
32
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>;
|
|
33
38
|
/**
|
|
34
39
|
* enforce the specified word to be a link.
|
|
35
40
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html
|
|
@@ -54,6 +59,9 @@ type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
|
54
59
|
}[];
|
|
55
60
|
[k: string]: unknown | undefined;
|
|
56
61
|
}];
|
|
62
|
+
type MarkdownPreferencesPreferLinkReferenceDefinitions = [] | [{
|
|
63
|
+
minLinks?: number;
|
|
64
|
+
}];
|
|
57
65
|
type MarkdownPreferencesPreferLinkedWords = [] | [{
|
|
58
66
|
words: ({
|
|
59
67
|
[k: string]: (string | null);
|
|
@@ -82,7 +90,7 @@ declare namespace meta_d_exports {
|
|
|
82
90
|
export { name, version };
|
|
83
91
|
}
|
|
84
92
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
85
|
-
declare const version: "0.
|
|
93
|
+
declare const version: "0.6.0";
|
|
86
94
|
//#endregion
|
|
87
95
|
//#region src/index.d.ts
|
|
88
96
|
declare const configs: {
|
package/lib/index.js
CHANGED
|
@@ -245,7 +245,7 @@ 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
|
*/
|
|
@@ -403,6 +403,140 @@ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
|
|
|
403
403
|
}
|
|
404
404
|
});
|
|
405
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
|
+
|
|
406
540
|
//#endregion
|
|
407
541
|
//#region src/rules/prefer-linked-words.ts
|
|
408
542
|
var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
@@ -518,6 +652,7 @@ const rules$1 = [
|
|
|
518
652
|
no_text_backslash_linebreak_default,
|
|
519
653
|
no_trailing_spaces_default,
|
|
520
654
|
prefer_inline_code_words_default,
|
|
655
|
+
prefer_link_reference_definitions_default,
|
|
521
656
|
prefer_linked_words_default
|
|
522
657
|
];
|
|
523
658
|
|
|
@@ -553,7 +688,7 @@ __export(meta_exports, {
|
|
|
553
688
|
version: () => version
|
|
554
689
|
});
|
|
555
690
|
const name = "eslint-plugin-markdown-preferences";
|
|
556
|
-
const version = "0.
|
|
691
|
+
const version = "0.6.0";
|
|
557
692
|
|
|
558
693
|
//#endregion
|
|
559
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",
|