eslint-plugin-markdown-preferences 0.5.0 → 0.7.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 +15 -10
- package/lib/index.d.ts +14 -1
- package/lib/index.js +186 -3
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A specialized ESLint plugin that helps enforce consistent writing style and formatting conventions in Markdown files. Perfect for documentation projects, blog posts, and any Markdown content where consistency matters.
|
|
4
4
|
|
|
5
|
-
[]
|
|
6
|
-
[]
|
|
7
|
-
[]
|
|
8
|
-
[]
|
|
9
|
-
[]
|
|
10
|
-
[]
|
|
11
|
-
[]
|
|
5
|
+
[][npm-package]
|
|
6
|
+
[][npm-package]
|
|
7
|
+
[][npmtrends]
|
|
8
|
+
[][npmtrends]
|
|
9
|
+
[][npmtrends]
|
|
10
|
+
[][npmtrends]
|
|
11
|
+
[][npmtrends]
|
|
12
12
|
[](https://github.com/ota-meshi/eslint-plugin-markdown-preferences/actions/workflows/NodeCI.yml)
|
|
13
13
|
|
|
14
14
|
## 📛 Features
|
|
@@ -81,7 +81,7 @@ Is not supported.
|
|
|
81
81
|
|
|
82
82
|
<!--RULES_SECTION_START-->
|
|
83
83
|
|
|
84
|
-
The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench 🔧 below
|
|
84
|
+
The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench 🔧 below.\
|
|
85
85
|
The rules with the following star ⭐ are included in the configs.
|
|
86
86
|
|
|
87
87
|
<!--RULES_TABLE_START-->
|
|
@@ -90,10 +90,12 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
90
90
|
|
|
91
91
|
| Rule ID | Description | Fixable | RECOMMENDED |
|
|
92
92
|
|:--------|:------------|:-------:|:-----------:|
|
|
93
|
+
| [markdown-preferences/definitions-last](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html) | require link definitions and footnote definitions to be placed at the end of the document | 🔧 | |
|
|
93
94
|
| [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
95
|
| [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
96
|
| [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
97
|
| [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. | 🔧 | |
|
|
98
|
+
| [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
99
|
| [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
100
|
|
|
99
101
|
<!--RULES_TABLE_END-->
|
|
@@ -108,9 +110,12 @@ Please use GitHub's Issues/PRs.
|
|
|
108
110
|
|
|
109
111
|
### Development Tools
|
|
110
112
|
|
|
111
|
-
- `npm test` runs tests and measures coverage.
|
|
112
|
-
- `npm run update` runs in order to update readme and recommended configuration.
|
|
113
|
+
- `npm test` runs tests and measures coverage.
|
|
114
|
+
- `npm run update` runs in order to update readme and recommended configuration.
|
|
113
115
|
|
|
114
116
|
## 🔒 License
|
|
115
117
|
|
|
116
118
|
See the [LICENSE](LICENSE) file for license rights and limitations (MIT).
|
|
119
|
+
|
|
120
|
+
[npm-package]: https://www.npmjs.com/package/eslint-plugin-markdown-preferences
|
|
121
|
+
[npmtrends]: http://www.npmtrends.com/eslint-plugin-markdown-preferences
|
package/lib/index.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ declare module 'eslint' {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
interface RuleOptions {
|
|
13
|
+
/**
|
|
14
|
+
* require link definitions and footnote definitions to be placed at the end of the document
|
|
15
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html
|
|
16
|
+
*/
|
|
17
|
+
'markdown-preferences/definitions-last'?: Linter.RuleEntry<[]>;
|
|
13
18
|
/**
|
|
14
19
|
* enforce consistent hard linebreak style.
|
|
15
20
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html
|
|
@@ -30,6 +35,11 @@ interface RuleOptions {
|
|
|
30
35
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html
|
|
31
36
|
*/
|
|
32
37
|
'markdown-preferences/prefer-inline-code-words'?: Linter.RuleEntry<MarkdownPreferencesPreferInlineCodeWords>;
|
|
38
|
+
/**
|
|
39
|
+
* enforce using link reference definitions instead of inline links
|
|
40
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-link-reference-definitions.html
|
|
41
|
+
*/
|
|
42
|
+
'markdown-preferences/prefer-link-reference-definitions'?: Linter.RuleEntry<MarkdownPreferencesPreferLinkReferenceDefinitions>;
|
|
33
43
|
/**
|
|
34
44
|
* enforce the specified word to be a link.
|
|
35
45
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html
|
|
@@ -54,6 +64,9 @@ type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
|
54
64
|
}[];
|
|
55
65
|
[k: string]: unknown | undefined;
|
|
56
66
|
}];
|
|
67
|
+
type MarkdownPreferencesPreferLinkReferenceDefinitions = [] | [{
|
|
68
|
+
minLinks?: number;
|
|
69
|
+
}];
|
|
57
70
|
type MarkdownPreferencesPreferLinkedWords = [] | [{
|
|
58
71
|
words: ({
|
|
59
72
|
[k: string]: (string | null);
|
|
@@ -82,7 +95,7 @@ declare namespace meta_d_exports {
|
|
|
82
95
|
export { name, version };
|
|
83
96
|
}
|
|
84
97
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
85
|
-
declare const version: "0.
|
|
98
|
+
declare const version: "0.7.0";
|
|
86
99
|
//#endregion
|
|
87
100
|
//#region src/index.d.ts
|
|
88
101
|
declare const configs: {
|
package/lib/index.js
CHANGED
|
@@ -25,6 +25,46 @@ function createRule(ruleName, rule) {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/rules/definitions-last.ts
|
|
30
|
+
var definitions_last_default = createRule("definitions-last", {
|
|
31
|
+
meta: {
|
|
32
|
+
type: "layout",
|
|
33
|
+
docs: {
|
|
34
|
+
description: "require link definitions and footnote definitions to be placed at the end of the document",
|
|
35
|
+
categories: []
|
|
36
|
+
},
|
|
37
|
+
fixable: "code",
|
|
38
|
+
hasSuggestions: false,
|
|
39
|
+
schema: [],
|
|
40
|
+
messages: {}
|
|
41
|
+
},
|
|
42
|
+
create(context) {
|
|
43
|
+
const sourceCode = context.sourceCode;
|
|
44
|
+
const lastNonDefinition = sourceCode.ast.children.findLast((node) => node.type !== "definition" && node.type !== "footnoteDefinition" && !(node.type === "html" && (node.value.startsWith("<!--") || node.value.startsWith("<script") || node.value.startsWith("<style"))));
|
|
45
|
+
if (!lastNonDefinition) return {};
|
|
46
|
+
const lastNonDefinitionRange = sourceCode.getRange(lastNonDefinition);
|
|
47
|
+
return { "definition, footnoteDefinition"(node) {
|
|
48
|
+
const range = sourceCode.getRange(node);
|
|
49
|
+
if (lastNonDefinitionRange[1] <= range[0]) return;
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
message: "Definition or footnote definition should be placed at the end of the document.",
|
|
53
|
+
*fix(fixer) {
|
|
54
|
+
let rangeStart = range[0];
|
|
55
|
+
for (let index = range[0] - 1; index >= 0; index--) {
|
|
56
|
+
const c = sourceCode.text[index];
|
|
57
|
+
if (c.trim()) break;
|
|
58
|
+
rangeStart = index;
|
|
59
|
+
}
|
|
60
|
+
yield fixer.removeRange([rangeStart, range[1]]);
|
|
61
|
+
yield fixer.insertTextAfterRange(lastNonDefinitionRange, sourceCode.text.slice(rangeStart, range[1]));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
} };
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
28
68
|
//#endregion
|
|
29
69
|
//#region src/rules/hard-linebreak-style.ts
|
|
30
70
|
var hard_linebreak_style_default = createRule("hard-linebreak-style", {
|
|
@@ -245,7 +285,7 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
245
285
|
|
|
246
286
|
//#endregion
|
|
247
287
|
//#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}!"#$%&'()
|
|
288
|
+
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
289
|
/**
|
|
250
290
|
* Iterate through words in a text node that match the specified words.
|
|
251
291
|
*/
|
|
@@ -403,6 +443,147 @@ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
|
|
|
403
443
|
}
|
|
404
444
|
});
|
|
405
445
|
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/rules/prefer-link-reference-definitions.ts
|
|
448
|
+
var prefer_link_reference_definitions_default = createRule("prefer-link-reference-definitions", {
|
|
449
|
+
meta: {
|
|
450
|
+
type: "layout",
|
|
451
|
+
docs: {
|
|
452
|
+
description: "enforce using link reference definitions instead of inline links",
|
|
453
|
+
categories: []
|
|
454
|
+
},
|
|
455
|
+
fixable: "code",
|
|
456
|
+
hasSuggestions: false,
|
|
457
|
+
schema: [{
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: { minLinks: {
|
|
460
|
+
type: "number",
|
|
461
|
+
description: "minimum number of links to trigger the rule (default: 2)",
|
|
462
|
+
default: 2,
|
|
463
|
+
minimum: 1
|
|
464
|
+
} },
|
|
465
|
+
additionalProperties: false
|
|
466
|
+
}],
|
|
467
|
+
messages: { useLinkReferenceDefinitions: "Use link reference definitions instead of inline links." }
|
|
468
|
+
},
|
|
469
|
+
create(context) {
|
|
470
|
+
const sourceCode = context.sourceCode;
|
|
471
|
+
const options = context.options[0] || {};
|
|
472
|
+
const minLinks = options.minLinks ?? 2;
|
|
473
|
+
const definitions = [];
|
|
474
|
+
const links = [];
|
|
475
|
+
const linkReferences = [];
|
|
476
|
+
const headings = [];
|
|
477
|
+
/**
|
|
478
|
+
* Verify links.
|
|
479
|
+
*/
|
|
480
|
+
function verify() {
|
|
481
|
+
const resourceToNodes = /* @__PURE__ */ new Map();
|
|
482
|
+
for (const link of links) getResourceNodes(link).links.push(link);
|
|
483
|
+
for (const linkReference of linkReferences) {
|
|
484
|
+
const definition = definitions.find((def) => def.identifier === linkReference.identifier);
|
|
485
|
+
if (definition) getResourceNodes(definition).linkReferences.push(linkReference);
|
|
486
|
+
}
|
|
487
|
+
for (const definition of definitions) getResourceNodes(definition).definitions.push(definition);
|
|
488
|
+
for (const map of resourceToNodes.values()) for (const nodes of map.values()) {
|
|
489
|
+
if (nodes.links.length === 0 || nodes.links.length + nodes.linkReferences.length < minLinks) continue;
|
|
490
|
+
for (const link of nodes.links) {
|
|
491
|
+
const linkInfo = getLinkInfo(link);
|
|
492
|
+
if (linkInfo.label === "") continue;
|
|
493
|
+
context.report({
|
|
494
|
+
node: link,
|
|
495
|
+
messageId: "useLinkReferenceDefinitions",
|
|
496
|
+
*fix(fixer) {
|
|
497
|
+
const definition = nodes.definitions[0];
|
|
498
|
+
let identifier;
|
|
499
|
+
if (definition) identifier = definition.label ?? definition.identifier;
|
|
500
|
+
else identifier = linkInfo.label.replaceAll(/[[\]]/gu, "-");
|
|
501
|
+
yield fixer.replaceText(link, `${sourceCode.text.slice(...linkInfo.labelRange)}${identifier === linkInfo.label ? "" : `[${identifier}]`}`);
|
|
502
|
+
if (!definition) {
|
|
503
|
+
const linkRange = sourceCode.getRange(link);
|
|
504
|
+
const nextSectionHeading = headings.find((heading) => linkRange[1] < sourceCode.getRange(heading)[0]);
|
|
505
|
+
let insertIndex;
|
|
506
|
+
if (nextSectionHeading) {
|
|
507
|
+
const headingRange = sourceCode.getRange(nextSectionHeading);
|
|
508
|
+
const headingStartLoc = sourceCode.getLoc(nextSectionHeading).start;
|
|
509
|
+
insertIndex = headingRange[0] - headingStartLoc.column;
|
|
510
|
+
} else insertIndex = sourceCode.text.trimEnd().length;
|
|
511
|
+
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()}${nextSectionHeading ? "\n" : ""}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get the resource nodes for a link or definition.
|
|
519
|
+
*/
|
|
520
|
+
function getResourceNodes(resource) {
|
|
521
|
+
const url = resource.url;
|
|
522
|
+
const title = resource.title ?? null;
|
|
523
|
+
let map = resourceToNodes.get(url);
|
|
524
|
+
if (!map) {
|
|
525
|
+
map = /* @__PURE__ */ new Map();
|
|
526
|
+
resourceToNodes.set(url, map);
|
|
527
|
+
}
|
|
528
|
+
let nodes = map.get(title);
|
|
529
|
+
if (!nodes) {
|
|
530
|
+
nodes = {
|
|
531
|
+
links: [],
|
|
532
|
+
linkReferences: [],
|
|
533
|
+
definitions: []
|
|
534
|
+
};
|
|
535
|
+
map.set(title, nodes);
|
|
536
|
+
}
|
|
537
|
+
return nodes;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
link(node) {
|
|
542
|
+
links.push(node);
|
|
543
|
+
},
|
|
544
|
+
linkReference(node) {
|
|
545
|
+
linkReferences.push(node);
|
|
546
|
+
},
|
|
547
|
+
definition(node) {
|
|
548
|
+
definitions.push(node);
|
|
549
|
+
},
|
|
550
|
+
heading(node) {
|
|
551
|
+
headings.push(node);
|
|
552
|
+
},
|
|
553
|
+
"root:exit"() {
|
|
554
|
+
verify();
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* Get the range of the link label.
|
|
559
|
+
*/
|
|
560
|
+
function getLinkInfo(link) {
|
|
561
|
+
const range = sourceCode.getRange(link);
|
|
562
|
+
const linkLabelRange = getLinkLabelRange();
|
|
563
|
+
const linkLabelWithBracketsText = sourceCode.text.slice(...linkLabelRange);
|
|
564
|
+
const linkLabelText = linkLabelWithBracketsText.slice(1, -1).trim();
|
|
565
|
+
const urlStartIndex = sourceCode.text.indexOf("(", linkLabelRange[1]);
|
|
566
|
+
return {
|
|
567
|
+
label: linkLabelText,
|
|
568
|
+
labelRange: linkLabelRange,
|
|
569
|
+
urlAndTitleRange: [urlStartIndex, range[1]]
|
|
570
|
+
};
|
|
571
|
+
/**
|
|
572
|
+
* Get the range of the link label.
|
|
573
|
+
*/
|
|
574
|
+
function getLinkLabelRange() {
|
|
575
|
+
if (link.children.length === 0) {
|
|
576
|
+
const index$1 = sourceCode.text.indexOf("]", range[0] + 1);
|
|
577
|
+
return [range[0], index$1 + 1];
|
|
578
|
+
}
|
|
579
|
+
const lastRange = sourceCode.getRange(link.children[link.children.length - 1]);
|
|
580
|
+
const index = sourceCode.text.indexOf("]", lastRange[1]);
|
|
581
|
+
return [range[0], index + 1];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
406
587
|
//#endregion
|
|
407
588
|
//#region src/rules/prefer-linked-words.ts
|
|
408
589
|
var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
@@ -514,10 +695,12 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
514
695
|
//#endregion
|
|
515
696
|
//#region src/utils/rules.ts
|
|
516
697
|
const rules$1 = [
|
|
698
|
+
definitions_last_default,
|
|
517
699
|
hard_linebreak_style_default,
|
|
518
700
|
no_text_backslash_linebreak_default,
|
|
519
701
|
no_trailing_spaces_default,
|
|
520
702
|
prefer_inline_code_words_default,
|
|
703
|
+
prefer_link_reference_definitions_default,
|
|
521
704
|
prefer_linked_words_default
|
|
522
705
|
];
|
|
523
706
|
|
|
@@ -532,7 +715,7 @@ __export(recommended_exports, {
|
|
|
532
715
|
rules: () => rules$2
|
|
533
716
|
});
|
|
534
717
|
const name$1 = "markdown-preferences/recommended";
|
|
535
|
-
const files = ["**/*.md"];
|
|
718
|
+
const files = ["*.md", "**/*.md"];
|
|
536
719
|
const language = "markdown/commonmark";
|
|
537
720
|
const plugins = {
|
|
538
721
|
markdown,
|
|
@@ -553,7 +736,7 @@ __export(meta_exports, {
|
|
|
553
736
|
version: () => version
|
|
554
737
|
});
|
|
555
738
|
const name = "eslint-plugin-markdown-preferences";
|
|
556
|
-
const version = "0.
|
|
739
|
+
const version = "0.7.0";
|
|
557
740
|
|
|
558
741
|
//#endregion
|
|
559
742
|
//#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.7.0",
|
|
4
4
|
"description": "ESLint plugin that enforces our markdown preferences",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,13 +64,14 @@
|
|
|
64
64
|
"@changesets/get-release-plan": "^4.0.8",
|
|
65
65
|
"@eslint/core": "^0.15.0",
|
|
66
66
|
"@eslint/markdown": "^7.1.0",
|
|
67
|
-
"@ota-meshi/eslint-plugin": "^0.
|
|
67
|
+
"@ota-meshi/eslint-plugin": "^0.18.0",
|
|
68
68
|
"@shikijs/vitepress-twoslash": "^3.0.0",
|
|
69
69
|
"@types/eslint": "^9.6.1",
|
|
70
70
|
"@types/eslint-scope": "^8.0.0",
|
|
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",
|
|
@@ -81,7 +82,7 @@
|
|
|
81
82
|
"eslint-compat-utils": "^0.6.4",
|
|
82
83
|
"eslint-config-prettier": "^10.1.1",
|
|
83
84
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
84
|
-
"eslint-plugin-eslint-plugin": "^
|
|
85
|
+
"eslint-plugin-eslint-plugin": "^7.0.0",
|
|
85
86
|
"eslint-plugin-jsdoc": "^52.0.0",
|
|
86
87
|
"eslint-plugin-json-schema-validator": "^5.3.1",
|
|
87
88
|
"eslint-plugin-jsonc": "^2.19.1",
|