eslint-plugin-markdown-preferences 0.18.0 → 0.20.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 +4 -0
- package/lib/index.d.ts +53 -1
- package/lib/index.js +556 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,7 +118,10 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
118
118
|
| [markdown-preferences/atx-heading-closing-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence.html) | enforce consistent use of closing sequence in ATX headings. | 🔧 | |
|
|
119
119
|
| [markdown-preferences/blockquote-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/blockquote-marker-alignment.html) | enforce consistent alignment of blockquote markers | 🔧 | ⭐ |
|
|
120
120
|
| [markdown-preferences/bullet-list-marker-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/bullet-list-marker-style.html) | enforce consistent bullet list (unordered list) marker style | 🔧 | |
|
|
121
|
+
| [markdown-preferences/code-fence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-length.html) | enforce consistent code fence length in fenced code blocks. | 🔧 | |
|
|
122
|
+
| [markdown-preferences/code-fence-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-style.html) | enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks. | 🔧 | |
|
|
121
123
|
| [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 | 🔧 | |
|
|
124
|
+
| [markdown-preferences/emphasis-delimiters-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emphasis-delimiters-style.html) | enforce a consistent delimiter style for emphasis and strong emphasis | 🔧 | |
|
|
122
125
|
| [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | 🔧 | ⭐ |
|
|
123
126
|
| [markdown-preferences/level1-heading-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level1-heading-style.html) | enforce consistent style for level 1 headings | 🔧 | |
|
|
124
127
|
| [markdown-preferences/level2-heading-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level2-heading-style.html) | enforce consistent style for level 2 headings | 🔧 | |
|
|
@@ -135,6 +138,7 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
135
138
|
| [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 | 🔧 | |
|
|
136
139
|
| [markdown-preferences/setext-heading-underline-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/setext-heading-underline-length.html) | enforce setext heading underline length | 🔧 | |
|
|
137
140
|
| [markdown-preferences/sort-definitions](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/sort-definitions.html) | enforce a specific order for link definitions and footnote definitions | 🔧 | |
|
|
141
|
+
| [markdown-preferences/strikethrough-delimiters-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/strikethrough-delimiters-style.html) | enforce a consistent delimiter style for strikethrough | 🔧 | |
|
|
138
142
|
| [markdown-preferences/thematic-break-character-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/thematic-break-character-style.html) | enforce consistent character style for thematic breaks (horizontal rules) in Markdown. | 🔧 | |
|
|
139
143
|
| [markdown-preferences/thematic-break-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/thematic-break-length.html) | enforce consistent length for thematic breaks (horizontal rules) in Markdown. | 🔧 | |
|
|
140
144
|
| [markdown-preferences/thematic-break-sequence-pattern](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/thematic-break-sequence-pattern.html) | enforce consistent repeating patterns for thematic breaks (horizontal rules) in Markdown. | 🔧 | |
|
package/lib/index.d.ts
CHANGED
|
@@ -35,6 +35,16 @@ interface RuleOptions {
|
|
|
35
35
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/canonical-code-block-language.html
|
|
36
36
|
*/
|
|
37
37
|
'markdown-preferences/canonical-code-block-language'?: Linter.RuleEntry<MarkdownPreferencesCanonicalCodeBlockLanguage>;
|
|
38
|
+
/**
|
|
39
|
+
* enforce consistent code fence length in fenced code blocks.
|
|
40
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-length.html
|
|
41
|
+
*/
|
|
42
|
+
'markdown-preferences/code-fence-length'?: Linter.RuleEntry<MarkdownPreferencesCodeFenceLength>;
|
|
43
|
+
/**
|
|
44
|
+
* enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks.
|
|
45
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-style.html
|
|
46
|
+
*/
|
|
47
|
+
'markdown-preferences/code-fence-style'?: Linter.RuleEntry<MarkdownPreferencesCodeFenceStyle>;
|
|
38
48
|
/**
|
|
39
49
|
* require link definitions and footnote definitions to be placed at the end of the document
|
|
40
50
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html
|
|
@@ -45,6 +55,11 @@ interface RuleOptions {
|
|
|
45
55
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emoji-notation.html
|
|
46
56
|
*/
|
|
47
57
|
'markdown-preferences/emoji-notation'?: Linter.RuleEntry<MarkdownPreferencesEmojiNotation>;
|
|
58
|
+
/**
|
|
59
|
+
* enforce a consistent delimiter style for emphasis and strong emphasis
|
|
60
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emphasis-delimiters-style.html
|
|
61
|
+
*/
|
|
62
|
+
'markdown-preferences/emphasis-delimiters-style'?: Linter.RuleEntry<MarkdownPreferencesEmphasisDelimitersStyle>;
|
|
48
63
|
/**
|
|
49
64
|
* enforce consistent hard linebreak style.
|
|
50
65
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html
|
|
@@ -145,6 +160,11 @@ interface RuleOptions {
|
|
|
145
160
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/sort-definitions.html
|
|
146
161
|
*/
|
|
147
162
|
'markdown-preferences/sort-definitions'?: Linter.RuleEntry<MarkdownPreferencesSortDefinitions>;
|
|
163
|
+
/**
|
|
164
|
+
* enforce a consistent delimiter style for strikethrough
|
|
165
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/strikethrough-delimiters-style.html
|
|
166
|
+
*/
|
|
167
|
+
'markdown-preferences/strikethrough-delimiters-style'?: Linter.RuleEntry<MarkdownPreferencesStrikethroughDelimitersStyle>;
|
|
148
168
|
/**
|
|
149
169
|
* enforce consistent casing in table header cells.
|
|
150
170
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-header-casing.html
|
|
@@ -188,11 +208,40 @@ type MarkdownPreferencesCanonicalCodeBlockLanguage = [] | [{
|
|
|
188
208
|
[k: string]: string;
|
|
189
209
|
};
|
|
190
210
|
}];
|
|
211
|
+
type MarkdownPreferencesCodeFenceLength = [] | [{
|
|
212
|
+
length?: number;
|
|
213
|
+
fallbackLength?: (number | ("minimum" | "as-is"));
|
|
214
|
+
overrides?: {
|
|
215
|
+
lang: string;
|
|
216
|
+
length?: number;
|
|
217
|
+
fallbackLength?: (number | ("minimum" | "as-is"));
|
|
218
|
+
}[];
|
|
219
|
+
}];
|
|
220
|
+
type MarkdownPreferencesCodeFenceStyle = [] | [{
|
|
221
|
+
style?: ("backtick" | "tilde");
|
|
222
|
+
}];
|
|
191
223
|
type MarkdownPreferencesEmojiNotation = [] | [{
|
|
192
224
|
prefer?: ("unicode" | "colon");
|
|
193
225
|
ignoreUnknown?: boolean;
|
|
194
226
|
ignoreList?: string[];
|
|
195
227
|
}];
|
|
228
|
+
type MarkdownPreferencesEmphasisDelimitersStyle = [] | [{
|
|
229
|
+
emphasis?: ("*" | "_");
|
|
230
|
+
strong?: ("**" | "__");
|
|
231
|
+
strongEmphasis?: (("***" | "___") | {
|
|
232
|
+
outer: "*";
|
|
233
|
+
inner: "__";
|
|
234
|
+
} | {
|
|
235
|
+
outer: "**";
|
|
236
|
+
inner: "_";
|
|
237
|
+
} | {
|
|
238
|
+
outer: "_";
|
|
239
|
+
inner: "**";
|
|
240
|
+
} | {
|
|
241
|
+
outer: "__";
|
|
242
|
+
inner: "*";
|
|
243
|
+
});
|
|
244
|
+
}];
|
|
196
245
|
type MarkdownPreferencesHardLinebreakStyle = [] | [{
|
|
197
246
|
style?: ("backslash" | "spaces");
|
|
198
247
|
}];
|
|
@@ -283,6 +332,9 @@ type MarkdownPreferencesSortDefinitions = [] | [{
|
|
|
283
332
|
})[];
|
|
284
333
|
alphabetical?: boolean;
|
|
285
334
|
}];
|
|
335
|
+
type MarkdownPreferencesStrikethroughDelimitersStyle = [] | [{
|
|
336
|
+
delimiter?: ("~" | "~~");
|
|
337
|
+
}];
|
|
286
338
|
type MarkdownPreferencesTableHeaderCasing = [] | [{
|
|
287
339
|
style?: ("Title Case" | "Sentence case");
|
|
288
340
|
preserveWords?: string[];
|
|
@@ -316,7 +368,7 @@ declare namespace meta_d_exports {
|
|
|
316
368
|
export { name, version };
|
|
317
369
|
}
|
|
318
370
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
319
|
-
declare const version: "0.
|
|
371
|
+
declare const version: "0.20.0";
|
|
320
372
|
//#endregion
|
|
321
373
|
//#region src/index.d.ts
|
|
322
374
|
declare const configs: {
|
package/lib/index.js
CHANGED
|
@@ -130,6 +130,27 @@ function getSourceLocationFromRange(sourceCode, node, range) {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/utils/unicode.ts
|
|
135
|
+
/**
|
|
136
|
+
* Check if the string is whitespace
|
|
137
|
+
*/
|
|
138
|
+
function isWhitespace(string) {
|
|
139
|
+
return /^[\p{Zs}\t\n\f\r]+$/u.test(string);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if the string is a space or tab
|
|
143
|
+
*/
|
|
144
|
+
function isSpaceOrTab(string) {
|
|
145
|
+
return /^[\t ]+$/u.test(string);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if the character is a punctuation character
|
|
149
|
+
*/
|
|
150
|
+
function isPunctuation(char) {
|
|
151
|
+
return /^[\p{P}\p{S}]+$/u.test(char);
|
|
152
|
+
}
|
|
153
|
+
|
|
133
154
|
//#endregion
|
|
134
155
|
//#region src/utils/atx-heading.ts
|
|
135
156
|
/**
|
|
@@ -262,7 +283,7 @@ function parseATXHeadingOpeningSequenceFromText(text) {
|
|
|
262
283
|
function skipWhitespace(index) {
|
|
263
284
|
let result = index;
|
|
264
285
|
let c;
|
|
265
|
-
while (result < text.length && (c = text[result]) && (c
|
|
286
|
+
while (result < text.length && (c = text[result]) && isSpaceOrTab(c)) result++;
|
|
266
287
|
return result;
|
|
267
288
|
}
|
|
268
289
|
}
|
|
@@ -287,7 +308,7 @@ function parseATXHeadingClosingSequenceFromText(text) {
|
|
|
287
308
|
function skipEndWhitespace(index) {
|
|
288
309
|
let result = index;
|
|
289
310
|
let c;
|
|
290
|
-
while (result >= 0 && (c = text[result]) && (c
|
|
311
|
+
while (result >= 0 && (c = text[result]) && isSpaceOrTab(c)) result--;
|
|
291
312
|
return result;
|
|
292
313
|
}
|
|
293
314
|
}
|
|
@@ -852,6 +873,108 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
|
|
|
852
873
|
}
|
|
853
874
|
});
|
|
854
875
|
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/utils/fenced-code-block.ts
|
|
878
|
+
const RE_OPENING_FENCE = /^(`{3,}|~{3,})/u;
|
|
879
|
+
const RE_LANGUAGE = /^(\w*)/u;
|
|
880
|
+
/**
|
|
881
|
+
* Parse the fenced code block.
|
|
882
|
+
*/
|
|
883
|
+
function parseFencedCodeBlock(sourceCode, node) {
|
|
884
|
+
const loc = sourceCode.getLoc(node);
|
|
885
|
+
const range = sourceCode.getRange(node);
|
|
886
|
+
const text = sourceCode.text.slice(...range);
|
|
887
|
+
const match = RE_OPENING_FENCE.exec(text);
|
|
888
|
+
if (!match) return null;
|
|
889
|
+
const [, fenceText] = match;
|
|
890
|
+
const fenceChar = fenceText[0];
|
|
891
|
+
let closingFenceText = "";
|
|
892
|
+
const trimmed = text.trimEnd();
|
|
893
|
+
const trailingSpacesLength = text.length - trimmed.length;
|
|
894
|
+
for (let index = trimmed.length - 1; index >= 0; index--) {
|
|
895
|
+
const c = trimmed[index];
|
|
896
|
+
if (c === fenceChar || isSpaceOrTab(c)) {
|
|
897
|
+
closingFenceText = c + closingFenceText;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
if (c === "\n") break;
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
closingFenceText = closingFenceText.trimStart();
|
|
904
|
+
if (!closingFenceText || !closingFenceText.startsWith(fenceText)) return null;
|
|
905
|
+
const lines = getParsedLines(sourceCode);
|
|
906
|
+
const afterOpeningFence = lines.get(loc.start.line).text.slice(fenceText.length);
|
|
907
|
+
const trimmedAfterOpeningFence = afterOpeningFence.trimStart();
|
|
908
|
+
const spaceAfterOpeningFenceLength = afterOpeningFence.length - trimmedAfterOpeningFence.length;
|
|
909
|
+
let languageText = "";
|
|
910
|
+
if (trimmedAfterOpeningFence) {
|
|
911
|
+
const langMatch = RE_LANGUAGE.exec(trimmedAfterOpeningFence);
|
|
912
|
+
languageText = langMatch[1];
|
|
913
|
+
}
|
|
914
|
+
const afterLanguage = trimmedAfterOpeningFence.slice(languageText.length);
|
|
915
|
+
const trimmedAfterLanguage = afterLanguage.trimStart();
|
|
916
|
+
const spaceAfterLanguageLength = afterLanguage.length - trimmedAfterLanguage.length;
|
|
917
|
+
const metaText = trimmedAfterLanguage.trimEnd();
|
|
918
|
+
const openingFence = {
|
|
919
|
+
text: fenceText,
|
|
920
|
+
range: [range[0], range[0] + fenceText.length],
|
|
921
|
+
loc: {
|
|
922
|
+
start: loc.start,
|
|
923
|
+
end: {
|
|
924
|
+
line: loc.start.line,
|
|
925
|
+
column: loc.start.column + fenceText.length
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
const language$1 = languageText ? {
|
|
930
|
+
text: languageText,
|
|
931
|
+
range: [openingFence.range[1] + spaceAfterOpeningFenceLength, openingFence.range[1] + spaceAfterOpeningFenceLength + languageText.length],
|
|
932
|
+
loc: {
|
|
933
|
+
start: {
|
|
934
|
+
line: openingFence.loc.end.line,
|
|
935
|
+
column: openingFence.loc.end.column + spaceAfterOpeningFenceLength
|
|
936
|
+
},
|
|
937
|
+
end: {
|
|
938
|
+
line: openingFence.loc.end.line,
|
|
939
|
+
column: openingFence.loc.end.column + spaceAfterOpeningFenceLength + languageText.length
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
} : null;
|
|
943
|
+
const meta = language$1 && metaText ? {
|
|
944
|
+
text: metaText,
|
|
945
|
+
range: [language$1.range[1] + spaceAfterLanguageLength, language$1.range[1] + spaceAfterLanguageLength + metaText.length],
|
|
946
|
+
loc: {
|
|
947
|
+
start: {
|
|
948
|
+
line: language$1.loc.end.line,
|
|
949
|
+
column: language$1.loc.end.column + spaceAfterLanguageLength
|
|
950
|
+
},
|
|
951
|
+
end: {
|
|
952
|
+
line: language$1.loc.end.line,
|
|
953
|
+
column: language$1.loc.end.column + spaceAfterLanguageLength + metaText.length
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
} : null;
|
|
957
|
+
return {
|
|
958
|
+
openingFence,
|
|
959
|
+
language: language$1,
|
|
960
|
+
meta,
|
|
961
|
+
closingFence: {
|
|
962
|
+
text: closingFenceText,
|
|
963
|
+
range: [range[1] - trailingSpacesLength - closingFenceText.length, range[1] - trailingSpacesLength],
|
|
964
|
+
loc: {
|
|
965
|
+
start: {
|
|
966
|
+
line: loc.end.line,
|
|
967
|
+
column: loc.end.column - trailingSpacesLength - closingFenceText.length
|
|
968
|
+
},
|
|
969
|
+
end: {
|
|
970
|
+
line: loc.end.line,
|
|
971
|
+
column: loc.end.column - trailingSpacesLength
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
855
978
|
//#endregion
|
|
856
979
|
//#region src/rules/canonical-code-block-language.ts
|
|
857
980
|
const DEFAULT_LANGUAGES = {
|
|
@@ -906,23 +1029,204 @@ var canonical_code_block_language_default = createRule("canonical-code-block-lan
|
|
|
906
1029
|
const canonical = languages[node.lang];
|
|
907
1030
|
const current = node.lang;
|
|
908
1031
|
if (current === canonical) return;
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
const fenceRegex = /^(`{3,}|~{3,})(\w*)(?:\s.*)?$/mu;
|
|
912
|
-
const fenceMatch = fenceRegex.exec(nodeText.split("\n")[0]);
|
|
913
|
-
if (!fenceMatch) return;
|
|
914
|
-
const [, fence, langInfo] = fenceMatch;
|
|
915
|
-
const range = [nodeRange[0] + fence.length, nodeRange[0] + fence.length + langInfo.length];
|
|
1032
|
+
const parsed = parseFencedCodeBlock(sourceCode, node);
|
|
1033
|
+
if (!parsed || !parsed.language) return;
|
|
916
1034
|
context.report({
|
|
917
1035
|
node,
|
|
918
|
-
loc:
|
|
1036
|
+
loc: parsed.language.loc,
|
|
919
1037
|
messageId: "useCanonical",
|
|
920
1038
|
data: {
|
|
921
1039
|
canonical,
|
|
922
1040
|
current
|
|
923
1041
|
},
|
|
924
1042
|
fix(fixer) {
|
|
925
|
-
return fixer.replaceTextRange(range, canonical);
|
|
1043
|
+
return fixer.replaceTextRange(parsed.language.range, canonical);
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
} };
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
//#endregion
|
|
1051
|
+
//#region src/rules/code-fence-length.ts
|
|
1052
|
+
var code_fence_length_default = createRule("code-fence-length", {
|
|
1053
|
+
meta: {
|
|
1054
|
+
type: "layout",
|
|
1055
|
+
docs: {
|
|
1056
|
+
description: "enforce consistent code fence length in fenced code blocks.",
|
|
1057
|
+
categories: [],
|
|
1058
|
+
listCategory: "Stylistic"
|
|
1059
|
+
},
|
|
1060
|
+
fixable: "code",
|
|
1061
|
+
hasSuggestions: false,
|
|
1062
|
+
schema: [{
|
|
1063
|
+
type: "object",
|
|
1064
|
+
properties: {
|
|
1065
|
+
length: {
|
|
1066
|
+
type: "integer",
|
|
1067
|
+
minimum: 3
|
|
1068
|
+
},
|
|
1069
|
+
fallbackLength: { anyOf: [{
|
|
1070
|
+
type: "integer",
|
|
1071
|
+
minimum: 3
|
|
1072
|
+
}, { enum: ["minimum", "as-is"] }] },
|
|
1073
|
+
overrides: {
|
|
1074
|
+
type: "array",
|
|
1075
|
+
items: {
|
|
1076
|
+
type: "object",
|
|
1077
|
+
properties: {
|
|
1078
|
+
lang: { type: "string" },
|
|
1079
|
+
length: {
|
|
1080
|
+
type: "integer",
|
|
1081
|
+
minimum: 3
|
|
1082
|
+
},
|
|
1083
|
+
fallbackLength: { anyOf: [{
|
|
1084
|
+
type: "integer",
|
|
1085
|
+
minimum: 3
|
|
1086
|
+
}, { enum: ["minimum", "as-is"] }] }
|
|
1087
|
+
},
|
|
1088
|
+
required: ["lang"],
|
|
1089
|
+
additionalProperties: false
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
additionalProperties: false
|
|
1094
|
+
}],
|
|
1095
|
+
messages: {
|
|
1096
|
+
notMatch: "The opening and closing code fence lengths must match.",
|
|
1097
|
+
notPreferred: "Code fence length should be {{expected}} (was {{actual}})."
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
create(context) {
|
|
1101
|
+
const sourceCode = context.sourceCode;
|
|
1102
|
+
const options = context.options[0] ?? {};
|
|
1103
|
+
/**
|
|
1104
|
+
* Get the effective options for the given code block node.
|
|
1105
|
+
*/
|
|
1106
|
+
function getOptionForCode(node) {
|
|
1107
|
+
const override = options.overrides?.find((o) => o.lang === node.lang);
|
|
1108
|
+
return {
|
|
1109
|
+
length: override?.length ?? options.length ?? 3,
|
|
1110
|
+
fallbackLength: override?.fallbackLength ?? options.fallbackLength ?? "minimum"
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Report the given code block node for not preferred length.
|
|
1115
|
+
*/
|
|
1116
|
+
function reportNotPreferred(node, parsed, length) {
|
|
1117
|
+
const expectedFence = getExpectedFence(parsed, length);
|
|
1118
|
+
context.report({
|
|
1119
|
+
node,
|
|
1120
|
+
loc: parsed.openingFence.loc,
|
|
1121
|
+
data: {
|
|
1122
|
+
expected: expectedFence,
|
|
1123
|
+
actual: parsed.openingFence.text
|
|
1124
|
+
},
|
|
1125
|
+
messageId: "notPreferred",
|
|
1126
|
+
fix(fixer) {
|
|
1127
|
+
return [fixer.replaceTextRange(parsed.openingFence.range, expectedFence), fixer.replaceTextRange(parsed.closingFence.range, expectedFence)];
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Verify the length of the given code block node.
|
|
1133
|
+
*/
|
|
1134
|
+
function verifyFenceLength(node, parsed) {
|
|
1135
|
+
const { length, fallbackLength } = getOptionForCode(node);
|
|
1136
|
+
if (parsed.openingFence.text.length === length) return true;
|
|
1137
|
+
const expectedFence = getExpectedFence(parsed, length);
|
|
1138
|
+
if (!node.value.includes(expectedFence)) {
|
|
1139
|
+
reportNotPreferred(node, parsed, length);
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
if (fallbackLength === "as-is") return true;
|
|
1143
|
+
if (fallbackLength === "minimum") {
|
|
1144
|
+
let fallback = length + 1;
|
|
1145
|
+
while (node.value.includes(getExpectedFence(parsed, fallback))) fallback++;
|
|
1146
|
+
if (parsed.openingFence.text.length === fallback) return true;
|
|
1147
|
+
reportNotPreferred(node, parsed, fallback);
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
if (fallbackLength <= length) return true;
|
|
1151
|
+
if (parsed.openingFence.text.length === fallbackLength) return true;
|
|
1152
|
+
const fallbackExpectedFence = getExpectedFence(parsed, fallbackLength);
|
|
1153
|
+
if (node.value.includes(fallbackExpectedFence)) return true;
|
|
1154
|
+
reportNotPreferred(node, parsed, fallbackLength);
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Get the expected fence string for the given length.
|
|
1159
|
+
*/
|
|
1160
|
+
function getExpectedFence(parsed, length) {
|
|
1161
|
+
const fenceChar = parsed.openingFence.text[0];
|
|
1162
|
+
return fenceChar.repeat(Math.max(3, length));
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Verify that the opening and closing fence lengths match.
|
|
1166
|
+
*/
|
|
1167
|
+
function verifyClosingFenceLength(node, parsed) {
|
|
1168
|
+
if (parsed.openingFence.text.length === parsed.closingFence.text.length) return true;
|
|
1169
|
+
context.report({
|
|
1170
|
+
node,
|
|
1171
|
+
loc: parsed.closingFence.loc,
|
|
1172
|
+
messageId: "notMatch",
|
|
1173
|
+
fix(fixer) {
|
|
1174
|
+
return [fixer.replaceTextRange(parsed.closingFence.range, parsed.openingFence.text)];
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
return { code(node) {
|
|
1180
|
+
const parsed = parseFencedCodeBlock(sourceCode, node);
|
|
1181
|
+
if (!parsed) return;
|
|
1182
|
+
if (!verifyFenceLength(node, parsed)) return;
|
|
1183
|
+
verifyClosingFenceLength(node, parsed);
|
|
1184
|
+
} };
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
//#endregion
|
|
1189
|
+
//#region src/rules/code-fence-style.ts
|
|
1190
|
+
var code_fence_style_default = createRule("code-fence-style", {
|
|
1191
|
+
meta: {
|
|
1192
|
+
type: "layout",
|
|
1193
|
+
docs: {
|
|
1194
|
+
description: "enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks.",
|
|
1195
|
+
categories: [],
|
|
1196
|
+
listCategory: "Stylistic"
|
|
1197
|
+
},
|
|
1198
|
+
fixable: "code",
|
|
1199
|
+
hasSuggestions: false,
|
|
1200
|
+
schema: [{
|
|
1201
|
+
type: "object",
|
|
1202
|
+
properties: { style: {
|
|
1203
|
+
type: "string",
|
|
1204
|
+
enum: ["backtick", "tilde"]
|
|
1205
|
+
} },
|
|
1206
|
+
additionalProperties: false
|
|
1207
|
+
}],
|
|
1208
|
+
messages: { useCodeFenceStyle: "Use {{expected}} code fence style instead of {{actual}}." }
|
|
1209
|
+
},
|
|
1210
|
+
create(context) {
|
|
1211
|
+
const sourceCode = context.sourceCode;
|
|
1212
|
+
const styleOption = context.options[0]?.style ?? "backtick";
|
|
1213
|
+
const expectedChar = styleOption === "tilde" ? "~" : "`";
|
|
1214
|
+
return { code(node) {
|
|
1215
|
+
const parsed = parseFencedCodeBlock(sourceCode, node);
|
|
1216
|
+
if (!parsed) return;
|
|
1217
|
+
if (parsed.openingFence.text.includes(expectedChar)) return;
|
|
1218
|
+
const expectedOpeningFence = expectedChar.repeat(Math.max(3, parsed.openingFence.text.length));
|
|
1219
|
+
if (node.value.includes(expectedOpeningFence)) return;
|
|
1220
|
+
context.report({
|
|
1221
|
+
node,
|
|
1222
|
+
loc: parsed.openingFence.loc,
|
|
1223
|
+
data: {
|
|
1224
|
+
expected: expectedOpeningFence,
|
|
1225
|
+
actual: parsed.openingFence.text
|
|
1226
|
+
},
|
|
1227
|
+
messageId: "useCodeFenceStyle",
|
|
1228
|
+
fix(fixer) {
|
|
1229
|
+
return [fixer.replaceTextRange(parsed.openingFence.range, expectedOpeningFence), fixer.replaceTextRange(parsed.closingFence.range, expectedChar.repeat(Math.max(3, parsed.closingFence.text.length)))];
|
|
926
1230
|
}
|
|
927
1231
|
});
|
|
928
1232
|
} };
|
|
@@ -3043,6 +3347,199 @@ var emoji_notation_default = createRule("emoji-notation", {
|
|
|
3043
3347
|
}
|
|
3044
3348
|
});
|
|
3045
3349
|
|
|
3350
|
+
//#endregion
|
|
3351
|
+
//#region src/rules/emphasis-delimiters-style.ts
|
|
3352
|
+
/**
|
|
3353
|
+
* Check if the emphasis/strong node is intraword (inside a word)
|
|
3354
|
+
* CommonMark: Intraword emphasis with _ is not allowed
|
|
3355
|
+
*/
|
|
3356
|
+
function isIntrawordForUnderline(text, range) {
|
|
3357
|
+
const before = text[range[0] - 1];
|
|
3358
|
+
if (!isWhitespace(before) && !isPunctuation(before)) return true;
|
|
3359
|
+
const after = text[range[1]];
|
|
3360
|
+
if (!isWhitespace(after) && !isPunctuation(after)) return true;
|
|
3361
|
+
return false;
|
|
3362
|
+
}
|
|
3363
|
+
var emphasis_delimiters_style_default = createRule("emphasis-delimiters-style", {
|
|
3364
|
+
meta: {
|
|
3365
|
+
type: "layout",
|
|
3366
|
+
docs: {
|
|
3367
|
+
description: "enforce a consistent delimiter style for emphasis and strong emphasis",
|
|
3368
|
+
categories: [],
|
|
3369
|
+
listCategory: "Stylistic"
|
|
3370
|
+
},
|
|
3371
|
+
fixable: "code",
|
|
3372
|
+
hasSuggestions: false,
|
|
3373
|
+
schema: [{
|
|
3374
|
+
type: "object",
|
|
3375
|
+
properties: {
|
|
3376
|
+
emphasis: { enum: ["*", "_"] },
|
|
3377
|
+
strong: { enum: ["**", "__"] },
|
|
3378
|
+
strongEmphasis: { anyOf: [
|
|
3379
|
+
{ enum: ["***", "___"] },
|
|
3380
|
+
{
|
|
3381
|
+
type: "object",
|
|
3382
|
+
properties: {
|
|
3383
|
+
outer: { const: "*" },
|
|
3384
|
+
inner: { const: "__" }
|
|
3385
|
+
},
|
|
3386
|
+
required: ["outer", "inner"],
|
|
3387
|
+
additionalProperties: false
|
|
3388
|
+
},
|
|
3389
|
+
{
|
|
3390
|
+
type: "object",
|
|
3391
|
+
properties: {
|
|
3392
|
+
outer: { const: "**" },
|
|
3393
|
+
inner: { const: "_" }
|
|
3394
|
+
},
|
|
3395
|
+
required: ["outer", "inner"],
|
|
3396
|
+
additionalProperties: false
|
|
3397
|
+
},
|
|
3398
|
+
{
|
|
3399
|
+
type: "object",
|
|
3400
|
+
properties: {
|
|
3401
|
+
outer: { const: "_" },
|
|
3402
|
+
inner: { const: "**" }
|
|
3403
|
+
},
|
|
3404
|
+
required: ["outer", "inner"],
|
|
3405
|
+
additionalProperties: false
|
|
3406
|
+
},
|
|
3407
|
+
{
|
|
3408
|
+
type: "object",
|
|
3409
|
+
properties: {
|
|
3410
|
+
outer: { const: "__" },
|
|
3411
|
+
inner: { const: "*" }
|
|
3412
|
+
},
|
|
3413
|
+
required: ["outer", "inner"],
|
|
3414
|
+
additionalProperties: false
|
|
3415
|
+
}
|
|
3416
|
+
] }
|
|
3417
|
+
},
|
|
3418
|
+
additionalProperties: false
|
|
3419
|
+
}],
|
|
3420
|
+
messages: {
|
|
3421
|
+
wrongEmphasis: "Emphasis delimiter should be '{{expectedOpening}}' (was '{{actualOpening}}').",
|
|
3422
|
+
wrongStrong: "Strong emphasis delimiter should be '{{expectedOpening}}' (was '{{actualOpening}}').",
|
|
3423
|
+
wrongStrongEmphasis: "Delimiter for strong+emphasis should be '{{expectedOpening}}' (was '{{actualOpening}}').",
|
|
3424
|
+
wrongStrongEmphasisDelimiterPair: "Delimiters for strong+emphasis should be '{{expectedOpening}}' ... '{{expectedClosing}}' (was '{{actualOpening}}' ... '{{actualClosing}}')."
|
|
3425
|
+
}
|
|
3426
|
+
},
|
|
3427
|
+
create(context) {
|
|
3428
|
+
const sourceCode = context.sourceCode;
|
|
3429
|
+
const option = context.options[0] ?? {};
|
|
3430
|
+
const emphasisDelimiter = option.emphasis ?? "_";
|
|
3431
|
+
const emphasis = {
|
|
3432
|
+
opening: emphasisDelimiter,
|
|
3433
|
+
closing: emphasisDelimiter
|
|
3434
|
+
};
|
|
3435
|
+
const strongDelimiter = option.strong ?? "**";
|
|
3436
|
+
const strong = {
|
|
3437
|
+
opening: strongDelimiter,
|
|
3438
|
+
closing: strongDelimiter
|
|
3439
|
+
};
|
|
3440
|
+
const strongEmphasis = parseStrongEmphasis(emphasisDelimiter, strongDelimiter, option.strongEmphasis);
|
|
3441
|
+
const processed = /* @__PURE__ */ new Set();
|
|
3442
|
+
/**
|
|
3443
|
+
* Verify the delimiter of the node
|
|
3444
|
+
*/
|
|
3445
|
+
function verifyDelimiter(node, expected, messageId) {
|
|
3446
|
+
const range = sourceCode.getRange(node);
|
|
3447
|
+
if (sourceCode.text.startsWith(expected.opening, range[0]) && sourceCode.text.endsWith(expected.closing, range[1])) return;
|
|
3448
|
+
if (sourceCode.text[range[0] - 1] === expected.opening[0] || sourceCode.text[range[1]] === expected.closing.at(-1) || sourceCode.text[range[0] + expected.opening.length] === expected.opening.at(-1) || sourceCode.text[range[1] - expected.closing.length - 1] === expected.closing[0]) return;
|
|
3449
|
+
if ((expected.opening.startsWith("_") || expected.closing.at(-1) === "_") && isIntrawordForUnderline(sourceCode.text, range)) return;
|
|
3450
|
+
const actual = {
|
|
3451
|
+
opening: sourceCode.text.slice(range[0], range[0] + expected.opening.length),
|
|
3452
|
+
closing: sourceCode.text.slice(range[1] - expected.closing.length, range[1])
|
|
3453
|
+
};
|
|
3454
|
+
context.report({
|
|
3455
|
+
node,
|
|
3456
|
+
messageId: expected.opening === expected.closing && actual.opening === actual.closing ? messageId : "wrongStrongEmphasisDelimiterPair",
|
|
3457
|
+
data: {
|
|
3458
|
+
expectedOpening: expected.opening,
|
|
3459
|
+
actualOpening: actual.opening,
|
|
3460
|
+
expectedClosing: expected.closing,
|
|
3461
|
+
actualClosing: actual.closing
|
|
3462
|
+
},
|
|
3463
|
+
fix(fixer) {
|
|
3464
|
+
return [fixer.replaceTextRange([range[0], range[0] + expected.opening.length], expected.opening), fixer.replaceTextRange([range[1] - expected.closing.length, range[1]], expected.closing)];
|
|
3465
|
+
}
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Verify the emphasis node has the correct delimiter
|
|
3470
|
+
*/
|
|
3471
|
+
function verifyEmphasis(node) {
|
|
3472
|
+
verifyDelimiter(node, emphasis, "wrongEmphasis");
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Verify the emphasis node has the correct delimiter
|
|
3476
|
+
*/
|
|
3477
|
+
function verifyStrong(node) {
|
|
3478
|
+
verifyDelimiter(node, strong, "wrongStrong");
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Verify the strong emphasis node has the correct delimiter
|
|
3482
|
+
*/
|
|
3483
|
+
function verifyStrongEmphasis(node) {
|
|
3484
|
+
verifyDelimiter(node, strongEmphasis, "wrongStrongEmphasis");
|
|
3485
|
+
}
|
|
3486
|
+
return {
|
|
3487
|
+
emphasis(node) {
|
|
3488
|
+
if (processed.has(node)) return;
|
|
3489
|
+
processed.add(node);
|
|
3490
|
+
if (node.children.length === 1 && node.children[0].type === "strong") {
|
|
3491
|
+
processed.add(node.children[0]);
|
|
3492
|
+
verifyStrongEmphasis(node);
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
verifyEmphasis(node);
|
|
3496
|
+
},
|
|
3497
|
+
strong(node) {
|
|
3498
|
+
if (processed.has(node)) return;
|
|
3499
|
+
processed.add(node);
|
|
3500
|
+
if (node.children.length === 1 && node.children[0].type === "emphasis") {
|
|
3501
|
+
processed.add(node.children[0]);
|
|
3502
|
+
verifyStrongEmphasis(node);
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
verifyStrong(node);
|
|
3506
|
+
}
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
/**
|
|
3511
|
+
* Parse strongEmphasis option to normalized object form
|
|
3512
|
+
*/
|
|
3513
|
+
function parseStrongEmphasis(emphasis, strong, strongEmphasisOpt) {
|
|
3514
|
+
if (strongEmphasisOpt != null) {
|
|
3515
|
+
if (typeof strongEmphasisOpt === "string") return {
|
|
3516
|
+
opening: strongEmphasisOpt,
|
|
3517
|
+
closing: strongEmphasisOpt
|
|
3518
|
+
};
|
|
3519
|
+
return {
|
|
3520
|
+
opening: strongEmphasisOpt.outer + strongEmphasisOpt.inner,
|
|
3521
|
+
closing: strongEmphasisOpt.inner + strongEmphasisOpt.outer
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
if (emphasis === "*") {
|
|
3525
|
+
if (strong === "**") return {
|
|
3526
|
+
opening: "***",
|
|
3527
|
+
closing: "***"
|
|
3528
|
+
};
|
|
3529
|
+
return {
|
|
3530
|
+
opening: "*__",
|
|
3531
|
+
closing: "__*"
|
|
3532
|
+
};
|
|
3533
|
+
} else if (strong === "**") return {
|
|
3534
|
+
opening: "**_",
|
|
3535
|
+
closing: "_**"
|
|
3536
|
+
};
|
|
3537
|
+
return {
|
|
3538
|
+
opening: "___",
|
|
3539
|
+
closing: "___"
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3046
3543
|
//#endregion
|
|
3047
3544
|
//#region src/rules/hard-linebreak-style.ts
|
|
3048
3545
|
var hard_linebreak_style_default = createRule("hard-linebreak-style", {
|
|
@@ -6183,7 +6680,7 @@ var sort_definitions_default = createRule("sort-definitions", {
|
|
|
6183
6680
|
const last = group.at(-1);
|
|
6184
6681
|
if (last && (node.type !== "definition" && node.type !== "footnoteDefinition" || sourceCode.getParent(node) !== sourceCode.getParent(last))) {
|
|
6185
6682
|
const range = sourceCode.getRange(node);
|
|
6186
|
-
const lastDefinitionRange = sourceCode.getRange(
|
|
6683
|
+
const lastDefinitionRange = sourceCode.getRange(last);
|
|
6187
6684
|
if (lastDefinitionRange[1] <= range[0]) {
|
|
6188
6685
|
verify(group);
|
|
6189
6686
|
group.length = 0;
|
|
@@ -6341,6 +6838,48 @@ function normalizedURL(url) {
|
|
|
6341
6838
|
return urlObj.href.endsWith("/") ? urlObj.href : `${urlObj.href}/`;
|
|
6342
6839
|
}
|
|
6343
6840
|
|
|
6841
|
+
//#endregion
|
|
6842
|
+
//#region src/rules/strikethrough-delimiters-style.ts
|
|
6843
|
+
var strikethrough_delimiters_style_default = createRule("strikethrough-delimiters-style", {
|
|
6844
|
+
meta: {
|
|
6845
|
+
type: "layout",
|
|
6846
|
+
docs: {
|
|
6847
|
+
description: "enforce a consistent delimiter style for strikethrough",
|
|
6848
|
+
categories: [],
|
|
6849
|
+
listCategory: "Stylistic"
|
|
6850
|
+
},
|
|
6851
|
+
fixable: "code",
|
|
6852
|
+
hasSuggestions: false,
|
|
6853
|
+
schema: [{
|
|
6854
|
+
type: "object",
|
|
6855
|
+
properties: { delimiter: { enum: ["~", "~~"] } },
|
|
6856
|
+
additionalProperties: false
|
|
6857
|
+
}],
|
|
6858
|
+
messages: { wrongDelimiter: "Strikethrough delimiter should be '{{expected}}' (was '{{actual}}')." }
|
|
6859
|
+
},
|
|
6860
|
+
create(context) {
|
|
6861
|
+
const sourceCode = context.sourceCode;
|
|
6862
|
+
const option = context.options[0] ?? {};
|
|
6863
|
+
const delimiter = option.delimiter ?? "~~";
|
|
6864
|
+
return { delete(node) {
|
|
6865
|
+
const range = sourceCode.getRange(node);
|
|
6866
|
+
const actualDelimiter = ["~~", "~"].find((d) => sourceCode.text.startsWith(d, range[0]) && sourceCode.text.endsWith(d, range[1]));
|
|
6867
|
+
if (!actualDelimiter || actualDelimiter === delimiter) return;
|
|
6868
|
+
context.report({
|
|
6869
|
+
node,
|
|
6870
|
+
messageId: "wrongDelimiter",
|
|
6871
|
+
data: {
|
|
6872
|
+
expected: delimiter,
|
|
6873
|
+
actual: actualDelimiter
|
|
6874
|
+
},
|
|
6875
|
+
fix(fixer) {
|
|
6876
|
+
return [fixer.replaceTextRange([range[0], range[0] + actualDelimiter.length], delimiter), fixer.replaceTextRange([range[1] - actualDelimiter.length, range[1]], delimiter)];
|
|
6877
|
+
}
|
|
6878
|
+
});
|
|
6879
|
+
} };
|
|
6880
|
+
}
|
|
6881
|
+
});
|
|
6882
|
+
|
|
6344
6883
|
//#endregion
|
|
6345
6884
|
//#region src/rules/table-header-casing.ts
|
|
6346
6885
|
var table_header_casing_default = createRule("table-header-casing", {
|
|
@@ -6694,8 +7233,11 @@ const rules$1 = [
|
|
|
6694
7233
|
blockquote_marker_alignment_default,
|
|
6695
7234
|
bullet_list_marker_style_default,
|
|
6696
7235
|
canonical_code_block_language_default,
|
|
7236
|
+
code_fence_length_default,
|
|
7237
|
+
code_fence_style_default,
|
|
6697
7238
|
definitions_last_default,
|
|
6698
7239
|
emoji_notation_default,
|
|
7240
|
+
emphasis_delimiters_style_default,
|
|
6699
7241
|
hard_linebreak_style_default,
|
|
6700
7242
|
heading_casing_default,
|
|
6701
7243
|
level1_heading_style_default,
|
|
@@ -6716,6 +7258,7 @@ const rules$1 = [
|
|
|
6716
7258
|
prefer_linked_words_default,
|
|
6717
7259
|
setext_heading_underline_length_default,
|
|
6718
7260
|
sort_definitions_default,
|
|
7261
|
+
strikethrough_delimiters_style_default,
|
|
6719
7262
|
table_header_casing_default,
|
|
6720
7263
|
thematic_break_character_style_default,
|
|
6721
7264
|
thematic_break_length_default,
|
|
@@ -6762,7 +7305,7 @@ __export(meta_exports, {
|
|
|
6762
7305
|
version: () => version
|
|
6763
7306
|
});
|
|
6764
7307
|
const name = "eslint-plugin-markdown-preferences";
|
|
6765
|
-
const version = "0.
|
|
7308
|
+
const version = "0.20.0";
|
|
6766
7309
|
|
|
6767
7310
|
//#endregion
|
|
6768
7311
|
//#region src/index.ts
|