eslint-plugin-markdown-preferences 0.17.0 → 0.18.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 CHANGED
@@ -114,17 +114,21 @@ The rules with the following star ⭐ are included in the configs.
114
114
 
115
115
  | Rule ID | Description | Fixable | RECOMMENDED |
116
116
  |:--------|:------------|:-------:|:-----------:|
117
- | [markdown-preferences/atx-headings-closing-sequence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence-length.html) | enforce consistent length for the closing sequence (trailing #s) in ATX headings. | 🔧 | |
118
- | [markdown-preferences/atx-headings-closing-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence.html) | enforce consistent use of closing sequence in ATX headings. | 🔧 | |
117
+ | [markdown-preferences/atx-heading-closing-sequence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence-length.html) | enforce consistent length for the closing sequence (trailing #s) in ATX headings. | 🔧 | |
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
+ | [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 | 🔧 | |
120
121
  | [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 | 🔧 | |
121
122
  | [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
+ | [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
+ | [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 | 🔧 | |
122
125
  | [markdown-preferences/list-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html) | enforce consistent alignment of list markers | 🔧 | ⭐ |
123
126
  | [markdown-preferences/no-laziness-blockquotes](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html) | disallow laziness in blockquotes | | ⭐ |
124
127
  | [markdown-preferences/no-multiple-empty-lines](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multiple-empty-lines.html) | disallow multiple empty lines in Markdown files. | 🔧 | |
125
128
  | [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. | | ⭐ |
126
129
  | [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. | 🔧 | |
127
130
  | [markdown-preferences/ordered-list-marker-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-sequence.html) | enforce that ordered list markers use sequential numbers | 🔧 | |
131
+ | [markdown-preferences/ordered-list-marker-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-style.html) | enforce consistent ordered list marker style | 🔧 | |
128
132
  | [markdown-preferences/padding-line-between-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html) | require or disallow padding lines between blocks | 🔧 | ⭐ |
129
133
  | [markdown-preferences/prefer-autolinks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html) | enforce the use of autolinks for URLs | 🔧 | ⭐ |
130
134
  | [markdown-preferences/prefer-fenced-code-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-fenced-code-blocks.html) | enforce the use of fenced code blocks over indented code blocks | 🔧 | ⭐ |
package/lib/index.d.ts CHANGED
@@ -12,19 +12,24 @@ declare module 'eslint' {
12
12
  interface RuleOptions {
13
13
  /**
14
14
  * enforce consistent use of closing sequence in ATX headings.
15
- * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence.html
15
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence.html
16
16
  */
17
- 'markdown-preferences/atx-headings-closing-sequence'?: Linter.RuleEntry<MarkdownPreferencesAtxHeadingsClosingSequence>;
17
+ 'markdown-preferences/atx-heading-closing-sequence'?: Linter.RuleEntry<MarkdownPreferencesAtxHeadingClosingSequence>;
18
18
  /**
19
19
  * enforce consistent length for the closing sequence (trailing #s) in ATX headings.
20
- * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence-length.html
20
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence-length.html
21
21
  */
22
- 'markdown-preferences/atx-headings-closing-sequence-length'?: Linter.RuleEntry<MarkdownPreferencesAtxHeadingsClosingSequenceLength>;
22
+ 'markdown-preferences/atx-heading-closing-sequence-length'?: Linter.RuleEntry<MarkdownPreferencesAtxHeadingClosingSequenceLength>;
23
23
  /**
24
24
  * enforce consistent alignment of blockquote markers
25
25
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/blockquote-marker-alignment.html
26
26
  */
27
27
  'markdown-preferences/blockquote-marker-alignment'?: Linter.RuleEntry<[]>;
28
+ /**
29
+ * enforce consistent bullet list (unordered list) marker style
30
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/bullet-list-marker-style.html
31
+ */
32
+ 'markdown-preferences/bullet-list-marker-style'?: Linter.RuleEntry<MarkdownPreferencesBulletListMarkerStyle>;
28
33
  /**
29
34
  * enforce canonical language names in code blocks
30
35
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/canonical-code-block-language.html
@@ -50,6 +55,16 @@ interface RuleOptions {
50
55
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/heading-casing.html
51
56
  */
52
57
  'markdown-preferences/heading-casing'?: Linter.RuleEntry<MarkdownPreferencesHeadingCasing>;
58
+ /**
59
+ * enforce consistent style for level 1 headings
60
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level1-heading-style.html
61
+ */
62
+ 'markdown-preferences/level1-heading-style'?: Linter.RuleEntry<MarkdownPreferencesLevel1HeadingStyle>;
63
+ /**
64
+ * enforce consistent style for level 2 headings
65
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level2-heading-style.html
66
+ */
67
+ 'markdown-preferences/level2-heading-style'?: Linter.RuleEntry<MarkdownPreferencesLevel2HeadingStyle>;
53
68
  /**
54
69
  * enforce consistent alignment of list markers
55
70
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html
@@ -85,6 +100,11 @@ interface RuleOptions {
85
100
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html
86
101
  */
87
102
  'markdown-preferences/ordered-list-marker-start'?: Linter.RuleEntry<MarkdownPreferencesOrderedListMarkerStart>;
103
+ /**
104
+ * enforce consistent ordered list marker style
105
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-style.html
106
+ */
107
+ 'markdown-preferences/ordered-list-marker-style'?: Linter.RuleEntry<MarkdownPreferencesOrderedListMarkerStyle>;
88
108
  /**
89
109
  * require or disallow padding lines between blocks
90
110
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html
@@ -146,13 +166,23 @@ interface RuleOptions {
146
166
  */
147
167
  'markdown-preferences/thematic-break-sequence-pattern'?: Linter.RuleEntry<MarkdownPreferencesThematicBreakSequencePattern>;
148
168
  }
149
- type MarkdownPreferencesAtxHeadingsClosingSequence = [] | [{
169
+ type MarkdownPreferencesAtxHeadingClosingSequence = [] | [{
150
170
  closingSequence?: ("always" | "never");
151
171
  }];
152
- type MarkdownPreferencesAtxHeadingsClosingSequenceLength = [] | [{
172
+ type MarkdownPreferencesAtxHeadingClosingSequenceLength = [] | [{
153
173
  mode?: ("match-opening" | "length" | "consistent" | "consistent-line-length" | "fixed-line-length");
154
174
  length?: number;
155
175
  }];
176
+ type MarkdownPreferencesBulletListMarkerStyle = [] | [{
177
+ primary?: ("-" | "*" | "+");
178
+ secondary?: ("-" | "*" | "+" | "any");
179
+ overrides?: {
180
+ level?: number;
181
+ parentMarker?: ("-" | "*" | "+" | "any" | "ordered");
182
+ primary?: ("-" | "*" | "+");
183
+ secondary?: ("-" | "*" | "+" | "any");
184
+ }[];
185
+ }];
156
186
  type MarkdownPreferencesCanonicalCodeBlockLanguage = [] | [{
157
187
  languages?: {
158
188
  [k: string]: string;
@@ -172,6 +202,14 @@ type MarkdownPreferencesHeadingCasing = [] | [{
172
202
  ignorePatterns?: string[];
173
203
  minorWords?: string[];
174
204
  }];
205
+ type MarkdownPreferencesLevel1HeadingStyle = [] | [{
206
+ style?: ("atx" | "setext");
207
+ allowMultilineSetext?: boolean;
208
+ }];
209
+ type MarkdownPreferencesLevel2HeadingStyle = [] | [{
210
+ style?: ("atx" | "setext");
211
+ allowMultilineSetext?: boolean;
212
+ }];
175
213
  type MarkdownPreferencesListMarkerAlignment = [] | [{
176
214
  align?: ("left" | "right");
177
215
  }];
@@ -187,6 +225,14 @@ type MarkdownPreferencesNoTrailingSpaces = [] | [{
187
225
  type MarkdownPreferencesOrderedListMarkerStart = [] | [{
188
226
  start?: (1 | 0);
189
227
  }];
228
+ type MarkdownPreferencesOrderedListMarkerStyle = [] | [{
229
+ prefer?: ("n." | "n)");
230
+ overrides?: {
231
+ level?: number;
232
+ parentMarker?: ("n." | "n)" | "any" | "bullet");
233
+ prefer?: ("n." | "n)");
234
+ }[];
235
+ }];
190
236
  type MarkdownPreferencesPaddingLineBetweenBlocks = {
191
237
  prev: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"))[]] | {
192
238
  type: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"))[]]);
@@ -270,7 +316,7 @@ declare namespace meta_d_exports {
270
316
  export { name, version };
271
317
  }
272
318
  declare const name: "eslint-plugin-markdown-preferences";
273
- declare const version: "0.17.0";
319
+ declare const version: "0.18.0";
274
320
  //#endregion
275
321
  //#region src/index.d.ts
276
322
  declare const configs: {
package/lib/index.js CHANGED
@@ -73,13 +73,19 @@ function getListItemMarker(sourceCode, node) {
73
73
  if (matchDot) return {
74
74
  kind: ".",
75
75
  raw: matchDot[0],
76
- sequence: Number(matchDot[1])
76
+ sequence: {
77
+ value: Number(matchDot[1]),
78
+ raw: matchDot[1]
79
+ }
77
80
  };
78
81
  const matchParen = /^(\d+)\)/.exec(text);
79
82
  return {
80
83
  kind: ")",
81
84
  raw: matchParen[0],
82
- sequence: Number(matchParen[1])
85
+ sequence: {
86
+ value: Number(matchParen[1]),
87
+ raw: matchParen[1]
88
+ }
83
89
  };
84
90
  }
85
91
  /**
@@ -127,57 +133,140 @@ function getSourceLocationFromRange(sourceCode, node, range) {
127
133
  //#endregion
128
134
  //#region src/utils/atx-heading.ts
129
135
  /**
130
- * Parse the closing sequence of an ATX heading.
136
+ * Parse the ATX heading.
131
137
  */
132
- function parseATXHeadingClosingSequence(sourceCode, node) {
138
+ function parseATXHeading(sourceCode, node) {
133
139
  if (getHeadingKind(sourceCode, node) !== "atx") return null;
134
140
  const loc = sourceCode.getLoc(node);
135
141
  const range = sourceCode.getRange(node);
136
- const parsed = parseATXHeadingClosingSequenceFromText(sourceCode.text.slice(...range));
137
- if (parsed == null) return { closingSequence: null };
138
- const rawAfter = {
139
- text: parsed.rawAfter,
140
- range: [range[1] - parsed.rawAfter.length, range[1]],
142
+ const text = sourceCode.text.slice(...range);
143
+ const parsedOpening = parseATXHeadingOpeningSequenceFromText(text);
144
+ if (parsedOpening === null) return null;
145
+ const openingSequence = {
146
+ text: parsedOpening.openingSequence,
147
+ range: [range[0], range[0] + parsedOpening.openingSequence.length],
148
+ loc: {
149
+ start: loc.start,
150
+ end: {
151
+ line: loc.start.line,
152
+ column: loc.start.column + parsedOpening.openingSequence.length
153
+ }
154
+ }
155
+ };
156
+ const spaceAfterOpening = {
157
+ text: parsedOpening.rawAfter,
158
+ range: [openingSequence.range[1], openingSequence.range[1] + parsedOpening.rawAfter.length],
159
+ loc: {
160
+ start: openingSequence.loc.end,
161
+ end: {
162
+ line: openingSequence.loc.end.line,
163
+ column: openingSequence.loc.end.column + parsedOpening.rawAfter.length
164
+ }
165
+ }
166
+ };
167
+ const parsedClosing = parseATXHeadingClosingSequenceFromText(text);
168
+ if (parsedClosing == null) {
169
+ const textAfterOpening = sourceCode.text.slice(spaceAfterOpening.range[1], range[1]);
170
+ const contentText$1 = textAfterOpening.trimEnd();
171
+ return {
172
+ openingSequence: {
173
+ ...openingSequence,
174
+ raws: { spaceAfter: spaceAfterOpening }
175
+ },
176
+ content: {
177
+ text: contentText$1,
178
+ range: [spaceAfterOpening.range[1], spaceAfterOpening.range[1] + contentText$1.length],
179
+ loc: {
180
+ start: spaceAfterOpening.loc.end,
181
+ end: {
182
+ line: loc.end.line,
183
+ column: loc.end.column - (textAfterOpening.length - contentText$1.length)
184
+ }
185
+ }
186
+ },
187
+ closingSequence: null
188
+ };
189
+ }
190
+ const spaceAfterClosing = {
191
+ text: parsedClosing.rawAfter,
192
+ range: [range[1] - parsedClosing.rawAfter.length, range[1]],
141
193
  loc: {
142
194
  start: {
143
195
  line: loc.end.line,
144
- column: loc.end.column - parsed.rawAfter.length
196
+ column: loc.end.column - parsedClosing.rawAfter.length
145
197
  },
146
- end: {
147
- line: loc.end.line,
148
- column: loc.end.column
149
- }
198
+ end: loc.end
150
199
  }
151
200
  };
152
201
  const closingSequence = {
153
- text: parsed.closingSequence,
154
- range: [rawAfter.range[0] - parsed.closingSequence.length, rawAfter.range[0]],
202
+ text: parsedClosing.closingSequence,
203
+ range: [spaceAfterClosing.range[0] - parsedClosing.closingSequence.length, spaceAfterClosing.range[0]],
155
204
  loc: {
156
205
  start: {
157
- line: rawAfter.loc.start.line,
158
- column: rawAfter.loc.start.column - parsed.closingSequence.length
206
+ line: spaceAfterClosing.loc.start.line,
207
+ column: spaceAfterClosing.loc.start.column - parsedClosing.closingSequence.length
159
208
  },
160
- end: rawAfter.loc.start
209
+ end: spaceAfterClosing.loc.start
161
210
  }
162
211
  };
163
- const rawBefore = {
164
- text: parsed.rawBefore,
165
- range: [closingSequence.range[0] - parsed.rawBefore.length, closingSequence.range[0]],
212
+ const spaceBeforeClosing = {
213
+ text: parsedClosing.rawBefore,
214
+ range: [closingSequence.range[0] - parsedClosing.rawBefore.length, closingSequence.range[0]],
166
215
  loc: {
167
216
  start: {
168
217
  line: closingSequence.loc.start.line,
169
- column: closingSequence.loc.start.column - parsed.rawBefore.length
218
+ column: closingSequence.loc.start.column - parsedClosing.rawBefore.length
170
219
  },
171
220
  end: closingSequence.loc.start
172
221
  }
173
222
  };
223
+ const contentText = sourceCode.text.slice(spaceAfterOpening.range[1], spaceBeforeClosing.range[0]);
174
224
  return {
175
- rawBefore,
176
- closingSequence,
177
- rawAfter
225
+ openingSequence: {
226
+ ...openingSequence,
227
+ raws: { spaceAfter: spaceAfterOpening }
228
+ },
229
+ content: {
230
+ text: contentText,
231
+ range: [spaceAfterOpening.range[1], spaceBeforeClosing.range[0]],
232
+ loc: {
233
+ start: spaceAfterOpening.loc.end,
234
+ end: spaceBeforeClosing.loc.start
235
+ }
236
+ },
237
+ closingSequence: {
238
+ ...closingSequence,
239
+ raws: {
240
+ spaceBefore: spaceBeforeClosing,
241
+ spaceAfter: spaceAfterClosing
242
+ }
243
+ }
178
244
  };
179
245
  }
180
246
  /**
247
+ * Parse the opening sequence from a text string.
248
+ */
249
+ function parseATXHeadingOpeningSequenceFromText(text) {
250
+ if (!text.startsWith("#")) return null;
251
+ let openingSequenceAfterOffset = 1;
252
+ while (openingSequenceAfterOffset < text.length && text[openingSequenceAfterOffset] === "#") openingSequenceAfterOffset++;
253
+ const afterOffset = skipWhitespace(openingSequenceAfterOffset);
254
+ if (afterOffset === openingSequenceAfterOffset || afterOffset >= text.length) return null;
255
+ return {
256
+ openingSequence: text.slice(0, openingSequenceAfterOffset),
257
+ rawAfter: text.slice(openingSequenceAfterOffset, afterOffset)
258
+ };
259
+ /**
260
+ * Skip whitespace characters at the start of the text.
261
+ */
262
+ function skipWhitespace(index) {
263
+ let result = index;
264
+ let c;
265
+ while (result < text.length && (c = text[result]) && (c === " " || c === " ")) result++;
266
+ return result;
267
+ }
268
+ }
269
+ /**
181
270
  * Parse the closing sequence from a text string.
182
271
  */
183
272
  function parseATXHeadingClosingSequenceFromText(text) {
@@ -271,8 +360,8 @@ function getTextWidth(text) {
271
360
  }
272
361
 
273
362
  //#endregion
274
- //#region src/rules/atx-headings-closing-sequence-length.ts
275
- var atx_headings_closing_sequence_length_default = createRule("atx-headings-closing-sequence-length", {
363
+ //#region src/rules/atx-heading-closing-sequence-length.ts
364
+ var atx_heading_closing_sequence_length_default = createRule("atx-heading-closing-sequence-length", {
276
365
  meta: {
277
366
  type: "layout",
278
367
  docs: {
@@ -307,14 +396,11 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
307
396
  /**
308
397
  * Verify the closing sequence length of an ATX heading.
309
398
  */
310
- function verifyATXHeadingClosingSequenceLength(node, getExpected) {
311
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
312
- if (!parsed || parsed.closingSequence == null) return;
399
+ function verifyATXHeadingClosingSequenceLength(parsed, reportNode, expectedLength) {
313
400
  const actualLength = parsed.closingSequence.text.length;
314
- const expectedLength = getExpected(node, parsed);
315
401
  if (expectedLength === actualLength) return;
316
402
  context.report({
317
- node,
403
+ node: reportNode,
318
404
  loc: parsed.closingSequence.loc,
319
405
  messageId: "wrongClosingLength",
320
406
  data: {
@@ -326,40 +412,41 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
326
412
  }
327
413
  });
328
414
  }
329
- return option.mode === "match-opening" ? (() => {
330
- const getExpected = (node) => node.depth;
331
- return { heading(node) {
332
- verifyATXHeadingClosingSequenceLength(node, getExpected);
333
- } };
334
- })() : option.mode === "length" ? (() => {
415
+ if (option.mode === "match-opening") return { heading(node) {
416
+ const parsed = parseATXHeading(sourceCode, node);
417
+ if (!parsed || parsed.closingSequence == null) return;
418
+ verifyATXHeadingClosingSequenceLength(parsed, node, node.depth);
419
+ } };
420
+ if (option.mode === "length") {
335
421
  const expected = option.length || 2;
336
- const getExpected = () => expected;
337
422
  return { heading(node) {
338
- verifyATXHeadingClosingSequenceLength(node, getExpected);
423
+ const parsed = parseATXHeading(sourceCode, node);
424
+ if (!parsed || parsed.closingSequence == null) return;
425
+ verifyATXHeadingClosingSequenceLength(parsed, node, expected);
339
426
  } };
340
- })() : option.mode === "fixed-line-length" ? (() => {
427
+ }
428
+ if (option.mode === "fixed-line-length") {
341
429
  const totalLength = option.length || 80;
342
- const getExpected = (_node, parsed) => {
343
- return totalLength - getContentLength(parsed);
344
- };
345
430
  return { heading(node) {
346
- verifyATXHeadingClosingSequenceLength(node, getExpected);
431
+ const parsed = parseATXHeading(sourceCode, node);
432
+ if (!parsed || parsed.closingSequence == null) return;
433
+ verifyATXHeadingClosingSequenceLength(parsed, node, totalLength - getContentLength(parsed));
347
434
  } };
348
- })() : option.mode === "consistent" ? (() => {
349
- let getExpected = null;
435
+ }
436
+ if (option.mode === "consistent") {
437
+ let expectedLength = null;
350
438
  return { heading(node) {
351
- if (getExpected == null) {
352
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
353
- if (!parsed || parsed.closingSequence == null) return;
354
- const expected = parsed.closingSequence.text.length;
355
- getExpected = () => expected;
356
- } else verifyATXHeadingClosingSequenceLength(node, getExpected);
439
+ const parsed = parseATXHeading(sourceCode, node);
440
+ if (!parsed || parsed.closingSequence == null) return;
441
+ if (expectedLength == null) expectedLength = parsed.closingSequence.text.length;
442
+ else verifyATXHeadingClosingSequenceLength(parsed, node, expectedLength);
357
443
  } };
358
- })() : option.mode === "consistent-line-length" ? (() => {
444
+ }
445
+ if (option.mode === "consistent-line-length") {
359
446
  const headings = [];
360
447
  return {
361
448
  heading(node) {
362
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
449
+ const parsed = parseATXHeading(sourceCode, node);
363
450
  if (!parsed || !parsed.closingSequence) return;
364
451
  headings.push({
365
452
  node,
@@ -385,13 +472,11 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
385
472
  const lineLength = getLineLength(heading.parsed);
386
473
  if (mostLongContentHeading.contentLength < lineLength && lineLength < minLineLength) minLineLength = Math.min(minLineLength, lineLength);
387
474
  }
388
- const getExpected = (_node, parsed) => {
389
- return minLineLength - getContentLength(parsed);
390
- };
391
- for (const { node } of headings) verifyATXHeadingClosingSequenceLength(node, getExpected);
475
+ for (const heading of headings) verifyATXHeadingClosingSequenceLength(heading.parsed, heading.node, minLineLength - getContentLength(heading.parsed));
392
476
  }
393
477
  };
394
- })() : {};
478
+ }
479
+ return {};
395
480
  /**
396
481
  * Get the content length of the heading.
397
482
  */
@@ -414,8 +499,8 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
414
499
  });
415
500
 
416
501
  //#endregion
417
- //#region src/rules/atx-headings-closing-sequence.ts
418
- var atx_headings_closing_sequence_default = createRule("atx-headings-closing-sequence", {
502
+ //#region src/rules/atx-heading-closing-sequence.ts
503
+ var atx_heading_closing_sequence_default = createRule("atx-heading-closing-sequence", {
419
504
  meta: {
420
505
  type: "layout",
421
506
  docs: {
@@ -443,7 +528,7 @@ var atx_headings_closing_sequence_default = createRule("atx-headings-closing-seq
443
528
  if (opt.closingSequence === "always" || opt.closingSequence === "never") closingSequence = opt.closingSequence;
444
529
  }
445
530
  return { heading(node) {
446
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
531
+ const parsed = parseATXHeading(sourceCode, node);
447
532
  if (!parsed) return;
448
533
  if (closingSequence === "always") {
449
534
  if (parsed.closingSequence) return;
@@ -460,12 +545,12 @@ var atx_headings_closing_sequence_default = createRule("atx-headings-closing-seq
460
545
  context.report({
461
546
  node,
462
547
  loc: {
463
- start: parsed.rawBefore.loc.start,
548
+ start: parsed.closingSequence.raws.spaceBefore.loc.start,
464
549
  end: parsed.closingSequence.loc.end
465
550
  },
466
551
  messageId: "forbidClosing",
467
552
  *fix(fixer) {
468
- const removeRange = [parsed.rawBefore.range[0], parsed.closingSequence.range[1]];
553
+ const removeRange = [parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.range[1]];
469
554
  const newHeadingText = sourceCode.text.slice(sourceCode.getRange(node)[0], removeRange[0]);
470
555
  const newHeadingParsed = parseATXHeadingClosingSequenceFromText(newHeadingText);
471
556
  if (newHeadingParsed) {
@@ -597,6 +682,176 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
597
682
  }
598
683
  });
599
684
 
685
+ //#endregion
686
+ //#region src/rules/bullet-list-marker-style.ts
687
+ const MARKERS$1 = [
688
+ "-",
689
+ "*",
690
+ "+"
691
+ ];
692
+ /**
693
+ * Get the other marker.
694
+ */
695
+ function getOtherMarker(unavailableMarker) {
696
+ return MARKERS$1.find((mark) => unavailableMarker !== mark);
697
+ }
698
+ /**
699
+ * Parse rule options.
700
+ */
701
+ function parseOptions$1(options) {
702
+ const primary = options.primary || "-";
703
+ const secondary = options.secondary || getOtherMarker(primary);
704
+ if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
705
+ const overrides = (options.overrides ?? []).map((override, index) => {
706
+ const primaryForOverride = override.primary || "-";
707
+ const secondaryForOverride = override.secondary || getOtherMarker(primaryForOverride);
708
+ if (primaryForOverride === secondaryForOverride) throw new Error(`overrides[${index}]: \`primary\` and \`secondary\` cannot be the same (primary: "${primaryForOverride}", secondary: "${secondaryForOverride}").`);
709
+ return {
710
+ level: override.level,
711
+ parentMarker: override.parentMarker ?? "any",
712
+ primary: primaryForOverride,
713
+ secondary: secondaryForOverride
714
+ };
715
+ }).reverse();
716
+ return { get(level, parentMarker) {
717
+ for (const o of overrides) if ((o.level == null || o.level === level) && (o.parentMarker === "any" || o.parentMarker === parentMarker)) return {
718
+ primary: o.primary,
719
+ secondary: o.secondary
720
+ };
721
+ return {
722
+ primary,
723
+ secondary
724
+ };
725
+ } };
726
+ }
727
+ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
728
+ meta: {
729
+ type: "layout",
730
+ docs: {
731
+ description: "enforce consistent bullet list (unordered list) marker style",
732
+ categories: [],
733
+ listCategory: "Stylistic"
734
+ },
735
+ fixable: "code",
736
+ hasSuggestions: false,
737
+ schema: [{
738
+ type: "object",
739
+ properties: {
740
+ primary: { enum: MARKERS$1 },
741
+ secondary: { enum: [...MARKERS$1, "any"] },
742
+ overrides: {
743
+ type: "array",
744
+ items: {
745
+ type: "object",
746
+ properties: {
747
+ level: {
748
+ type: "integer",
749
+ minimum: 1
750
+ },
751
+ parentMarker: { enum: [
752
+ ...MARKERS$1,
753
+ "any",
754
+ "ordered"
755
+ ] },
756
+ primary: { enum: MARKERS$1 },
757
+ secondary: { enum: [...MARKERS$1, "any"] }
758
+ },
759
+ additionalProperties: false
760
+ }
761
+ }
762
+ },
763
+ additionalProperties: false
764
+ }],
765
+ messages: { unexpected: "Bullet list marker should be '{{marker}}'." }
766
+ },
767
+ create(context) {
768
+ const sourceCode = context.sourceCode;
769
+ const options = parseOptions$1(context.options[0] || {});
770
+ let containerStack = {
771
+ node: sourceCode.ast,
772
+ level: 1,
773
+ upper: null
774
+ };
775
+ /**
776
+ * Check bullet list marker style
777
+ */
778
+ function checkBulletList(node) {
779
+ let parentMarker;
780
+ if (containerStack.node.type === "listItem") {
781
+ const parentMarkerKind = getListItemMarker(sourceCode, containerStack.node).kind;
782
+ parentMarker = parentMarkerKind === "." || parentMarkerKind === ")" ? "ordered" : parentMarkerKind;
783
+ } else parentMarker = "top";
784
+ const { primary, secondary } = options.get(containerStack.level, parentMarker);
785
+ const nodeIndex = containerStack.node.children.indexOf(node);
786
+ if (nodeIndex === -1) return;
787
+ const prevNode = nodeIndex > 0 ? containerStack.node.children[nodeIndex - 1] : null;
788
+ const prevBulletList = prevNode && (prevNode.type === "list" ? prevNode : null);
789
+ const prevBulletListMarker = prevBulletList && getListItemMarker(sourceCode, prevBulletList);
790
+ const expectedMarker = prevBulletListMarker?.kind !== primary ? primary : secondary;
791
+ if (expectedMarker === "any") return;
792
+ const marker = getListItemMarker(sourceCode, node);
793
+ if (marker.kind === expectedMarker) return;
794
+ const loc = sourceCode.getLoc(node);
795
+ context.report({
796
+ node,
797
+ loc: {
798
+ start: loc.start,
799
+ end: {
800
+ line: loc.start.line,
801
+ column: loc.start.column + marker.raw.length
802
+ }
803
+ },
804
+ messageId: "unexpected",
805
+ data: { marker: expectedMarker },
806
+ *fix(fixer) {
807
+ if (prevNode?.type === "list" && prevBulletListMarker && (prevBulletListMarker.kind === "-" || prevBulletListMarker.kind === "*" || prevBulletListMarker.kind === "+")) yield fixMarker(prevNode.children[0], prevBulletListMarker.kind);
808
+ yield* fixMarkers(node, expectedMarker);
809
+ let prevMarker = expectedMarker;
810
+ for (let index = nodeIndex + 1; index < containerStack.node.children.length; index++) {
811
+ const nextNode = containerStack.node.children[index];
812
+ if (nextNode.type !== "list") break;
813
+ const nextMarker = getListItemMarker(sourceCode, nextNode);
814
+ if (nextMarker.kind !== prevMarker) break;
815
+ let expectedNextMarker = prevMarker === primary ? secondary : primary;
816
+ if (expectedNextMarker === "any") expectedNextMarker = getOtherMarker(prevMarker);
817
+ yield* fixMarkers(nextNode, expectedNextMarker);
818
+ prevMarker = expectedNextMarker;
819
+ }
820
+ /**
821
+ * Fix bullet list markers
822
+ */
823
+ function* fixMarkers(list, replacementMarker) {
824
+ for (const item of list.children) yield fixMarker(item, replacementMarker);
825
+ }
826
+ /**
827
+ * Fix bullet list item marker
828
+ */
829
+ function fixMarker(item, replacementMarker) {
830
+ const range = sourceCode.getRange(item);
831
+ return fixer.replaceTextRange([range[0], range[0] + 1], replacementMarker);
832
+ }
833
+ }
834
+ });
835
+ }
836
+ return {
837
+ list(node) {
838
+ if (node.ordered) return;
839
+ checkBulletList(node);
840
+ },
841
+ "root, blockquote, listItem, footnoteDefinition"(node) {
842
+ containerStack = {
843
+ node,
844
+ level: node.type === "listItem" ? containerStack.level + 1 : 1,
845
+ upper: containerStack
846
+ };
847
+ },
848
+ "root, blockquote, listItem, footnoteDefinition:exit"() {
849
+ containerStack = containerStack.upper;
850
+ }
851
+ };
852
+ }
853
+ });
854
+
600
855
  //#endregion
601
856
  //#region src/rules/canonical-code-block-language.ts
602
857
  const DEFAULT_LANGUAGES = {
@@ -3597,6 +3852,280 @@ var heading_casing_default = createRule("heading-casing", {
3597
3852
  }
3598
3853
  });
3599
3854
 
3855
+ //#endregion
3856
+ //#region src/utils/setext-heading.ts
3857
+ /**
3858
+ * Parse the setext heading.
3859
+ */
3860
+ function parseSetextHeading(sourceCode, node) {
3861
+ if (getHeadingKind(sourceCode, node) !== "setext") return null;
3862
+ const lines = getParsedLines(sourceCode);
3863
+ const contentLines = [];
3864
+ const nodeLoc = sourceCode.getLoc(node);
3865
+ for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
3866
+ const content = parseContent(lines.get(lineNumber));
3867
+ contentLines.push(content);
3868
+ }
3869
+ const underline = parseUnderline(lines.get(nodeLoc.end.line));
3870
+ if (!underline) return null;
3871
+ return {
3872
+ contentLines,
3873
+ underline
3874
+ };
3875
+ }
3876
+ /**
3877
+ * Parse the content line of a setext heading.
3878
+ */
3879
+ function parseContent(line) {
3880
+ let prefix = "";
3881
+ let spaceBefore = "";
3882
+ let suffix = "";
3883
+ for (let index = 0; index < line.text.length; index++) {
3884
+ const c = line.text[index];
3885
+ if (!c.trim()) {
3886
+ spaceBefore += c;
3887
+ continue;
3888
+ }
3889
+ if (c === ">" && spaceBefore.length < 4) {
3890
+ prefix += spaceBefore + c;
3891
+ spaceBefore = "";
3892
+ continue;
3893
+ }
3894
+ suffix = line.text.slice(index);
3895
+ break;
3896
+ }
3897
+ const content = suffix.trimEnd();
3898
+ const spaceAfter = suffix.slice(content.length);
3899
+ return {
3900
+ text: content,
3901
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
3902
+ loc: {
3903
+ start: {
3904
+ line: line.line,
3905
+ column: prefix.length + spaceBefore.length + 1
3906
+ },
3907
+ end: {
3908
+ line: line.line,
3909
+ column: prefix.length + spaceBefore.length + content.length + 1
3910
+ }
3911
+ },
3912
+ raws: {
3913
+ prefix,
3914
+ spaceBefore,
3915
+ spaceAfter
3916
+ }
3917
+ };
3918
+ }
3919
+ /**
3920
+ * Parse the underline of a setext heading.
3921
+ */
3922
+ function parseUnderline(line) {
3923
+ let marker = null;
3924
+ let underlineText = "";
3925
+ let prefix = "";
3926
+ let spaceBefore = "";
3927
+ let spaceAfter = "";
3928
+ for (let index = line.text.length - 1; index >= 0; index--) {
3929
+ const c = line.text[index];
3930
+ if (!marker) {
3931
+ if (c === "=" || c === "-") {
3932
+ underlineText = c + underlineText;
3933
+ marker = c;
3934
+ } else if (!c.trim()) spaceAfter = c + spaceAfter;
3935
+ else return null;
3936
+ continue;
3937
+ }
3938
+ if (c === marker) {
3939
+ underlineText = c + spaceBefore + underlineText;
3940
+ spaceBefore = "";
3941
+ } else if (!c.trim()) spaceBefore = c + spaceBefore;
3942
+ else {
3943
+ prefix = line.text.slice(0, index + 1);
3944
+ break;
3945
+ }
3946
+ }
3947
+ if (!marker) return null;
3948
+ const underlineLoc = {
3949
+ start: {
3950
+ line: line.line,
3951
+ column: prefix.length + spaceBefore.length + 1
3952
+ },
3953
+ end: {
3954
+ line: line.line,
3955
+ column: prefix.length + spaceBefore.length + underlineText.length + 1
3956
+ }
3957
+ };
3958
+ return {
3959
+ text: underlineText,
3960
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
3961
+ loc: underlineLoc,
3962
+ marker,
3963
+ raws: {
3964
+ prefix,
3965
+ spaceBefore,
3966
+ spaceAfter
3967
+ }
3968
+ };
3969
+ }
3970
+
3971
+ //#endregion
3972
+ //#region src/rules/level1-heading-style.ts
3973
+ var level1_heading_style_default = createRule("level1-heading-style", {
3974
+ meta: {
3975
+ type: "layout",
3976
+ docs: {
3977
+ description: "enforce consistent style for level 1 headings",
3978
+ categories: [],
3979
+ listCategory: "Stylistic"
3980
+ },
3981
+ fixable: "code",
3982
+ hasSuggestions: false,
3983
+ schema: [{
3984
+ type: "object",
3985
+ properties: {
3986
+ style: { enum: ["atx", "setext"] },
3987
+ allowMultilineSetext: { type: "boolean" }
3988
+ },
3989
+ additionalProperties: false
3990
+ }],
3991
+ messages: {
3992
+ expectedAtx: "Expected ATX style heading (# Heading).",
3993
+ expectedSetext: "Expected Setext style heading (Heading\\n======).",
3994
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
3995
+ }
3996
+ },
3997
+ create(context) {
3998
+ const sourceCode = context.sourceCode;
3999
+ const opt = context.options[0] || {};
4000
+ const style = opt.style ?? "atx";
4001
+ const allowMultilineSetext = opt.allowMultilineSetext;
4002
+ return { heading(node) {
4003
+ if (node.depth !== 1) return;
4004
+ const headingKind = getHeadingKind(sourceCode, node);
4005
+ if (style === "atx") {
4006
+ if (headingKind !== "setext") return;
4007
+ const parsed = parseSetextHeading(sourceCode, node);
4008
+ if (!parsed) return;
4009
+ const isMultiline = parsed.contentLines.length > 1;
4010
+ if (isMultiline) {
4011
+ if (allowMultilineSetext) return;
4012
+ context.report({
4013
+ node,
4014
+ messageId: "multilineSetextNotAllowed"
4015
+ });
4016
+ return;
4017
+ }
4018
+ context.report({
4019
+ node,
4020
+ messageId: "expectedAtx",
4021
+ *fix(fixer) {
4022
+ const heading = parsed.contentLines[0];
4023
+ yield fixer.insertTextBeforeRange(heading.range, "# ");
4024
+ const lines = getParsedLines(sourceCode);
4025
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4026
+ }
4027
+ });
4028
+ } else if (style === "setext") {
4029
+ if (headingKind !== "atx" || node.children.length === 0) return;
4030
+ context.report({
4031
+ node,
4032
+ messageId: "expectedSetext",
4033
+ *fix(fixer) {
4034
+ const parsed = parseATXHeading(sourceCode, node);
4035
+ if (!parsed) return;
4036
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.openingSequence.raws.spaceAfter.range[1]]);
4037
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.raws.spaceAfter.range[1]]);
4038
+ const lines = getParsedLines(sourceCode);
4039
+ const underline = "=".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4040
+ const prefix = lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1);
4041
+ const appendingText = `\n${prefix}${underline}`;
4042
+ yield fixer.insertTextAfter(node, appendingText);
4043
+ }
4044
+ });
4045
+ }
4046
+ } };
4047
+ }
4048
+ });
4049
+
4050
+ //#endregion
4051
+ //#region src/rules/level2-heading-style.ts
4052
+ var level2_heading_style_default = createRule("level2-heading-style", {
4053
+ meta: {
4054
+ type: "layout",
4055
+ docs: {
4056
+ description: "enforce consistent style for level 2 headings",
4057
+ categories: [],
4058
+ listCategory: "Stylistic"
4059
+ },
4060
+ fixable: "code",
4061
+ hasSuggestions: false,
4062
+ schema: [{
4063
+ type: "object",
4064
+ properties: {
4065
+ style: { enum: ["atx", "setext"] },
4066
+ allowMultilineSetext: { type: "boolean" }
4067
+ },
4068
+ additionalProperties: false
4069
+ }],
4070
+ messages: {
4071
+ expectedAtx: "Expected ATX style heading (## Heading).",
4072
+ expectedSetext: "Expected Setext style heading (Heading\\n------).",
4073
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4074
+ }
4075
+ },
4076
+ create(context) {
4077
+ const sourceCode = context.sourceCode;
4078
+ const opt = context.options[0] || {};
4079
+ const style = opt.style ?? "atx";
4080
+ const allowMultilineSetext = opt.allowMultilineSetext;
4081
+ return { heading(node) {
4082
+ if (node.depth !== 2) return;
4083
+ const headingKind = getHeadingKind(sourceCode, node);
4084
+ if (style === "atx") {
4085
+ if (headingKind !== "setext") return;
4086
+ const parsed = parseSetextHeading(sourceCode, node);
4087
+ if (!parsed) return;
4088
+ const isMultiline = parsed.contentLines.length > 1;
4089
+ if (isMultiline) {
4090
+ if (allowMultilineSetext) return;
4091
+ context.report({
4092
+ node,
4093
+ messageId: "multilineSetextNotAllowed"
4094
+ });
4095
+ return;
4096
+ }
4097
+ context.report({
4098
+ node,
4099
+ messageId: "expectedAtx",
4100
+ *fix(fixer) {
4101
+ const heading = parsed.contentLines[0];
4102
+ yield fixer.insertTextBeforeRange(heading.range, "## ");
4103
+ const lines = getParsedLines(sourceCode);
4104
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4105
+ }
4106
+ });
4107
+ } else if (style === "setext") {
4108
+ if (headingKind !== "atx" || node.children.length === 0) return;
4109
+ context.report({
4110
+ node,
4111
+ messageId: "expectedSetext",
4112
+ *fix(fixer) {
4113
+ const parsed = parseATXHeading(sourceCode, node);
4114
+ if (!parsed) return;
4115
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.openingSequence.raws.spaceAfter.range[1]]);
4116
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.raws.spaceAfter.range[1]]);
4117
+ const lines = getParsedLines(sourceCode);
4118
+ const underline = "-".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4119
+ const prefix = lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1);
4120
+ const appendingText = `\n${prefix}${underline}`;
4121
+ yield fixer.insertTextAfter(node, appendingText);
4122
+ }
4123
+ });
4124
+ }
4125
+ } };
4126
+ }
4127
+ });
4128
+
3600
4129
  //#endregion
3601
4130
  //#region src/rules/list-marker-alignment.ts
3602
4131
  const ALIGN_TO_POSITION_NAME = {
@@ -4150,7 +4679,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
4150
4679
  messageId: "inconsistentStart",
4151
4680
  data: {
4152
4681
  expected: new Intl.ListFormat("en-US", { type: "disjunction" }).format(expected.map((n) => `'${n}'`)),
4153
- actual: String(marker.sequence)
4682
+ actual: marker.sequence.raw
4154
4683
  },
4155
4684
  fix: scope.last == null ? (fixer) => {
4156
4685
  const expectedMarker = `1${marker.kind}`;
@@ -4171,7 +4700,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
4171
4700
  const marker = getListItemMarker(sourceCode, item);
4172
4701
  if (marker.kind !== "." && marker.kind !== ")") continue;
4173
4702
  const expectedSequence = node.start + i;
4174
- if (marker.sequence !== expectedSequence) {
4703
+ if (marker.sequence.value !== expectedSequence) {
4175
4704
  const expectedMarker = `${expectedSequence}${marker.kind}`;
4176
4705
  context.report({
4177
4706
  node: item,
@@ -4306,6 +4835,174 @@ var ordered_list_marker_start_default = createRule("ordered-list-marker-start",
4306
4835
  }
4307
4836
  });
4308
4837
 
4838
+ //#endregion
4839
+ //#region src/rules/ordered-list-marker-style.ts
4840
+ const MARKER_KINDS = [".", ")"];
4841
+ const MARKERS = MARKER_KINDS.map((kind) => `n${kind}`);
4842
+ /**
4843
+ * Get the other marker kind.
4844
+ */
4845
+ function getOtherMarkerKind(unavailableMarker) {
4846
+ return MARKER_KINDS.find((mark) => unavailableMarker !== mark);
4847
+ }
4848
+ function markerToKind(marker) {
4849
+ if (marker === "n.") return ".";
4850
+ if (marker === "n)") return ")";
4851
+ return marker;
4852
+ }
4853
+ /**
4854
+ * Parse rule options.
4855
+ */
4856
+ function parseOptions(options) {
4857
+ const prefer = markerToKind(options.prefer) || ".";
4858
+ const overrides = (options.overrides ?? []).map((override) => {
4859
+ const preferForOverride = markerToKind(override.prefer) || ".";
4860
+ return {
4861
+ level: override.level,
4862
+ parentMarker: markerToKind(override.parentMarker) ?? "any",
4863
+ prefer: preferForOverride
4864
+ };
4865
+ }).reverse();
4866
+ return { get(level, parentMarker) {
4867
+ for (const o of overrides) if ((o.level == null || o.level === level) && (o.parentMarker === "any" || o.parentMarker === parentMarker)) return { prefer: o.prefer };
4868
+ return { prefer };
4869
+ } };
4870
+ }
4871
+ /**
4872
+ * Check if a item marker is an ordered list marker.
4873
+ */
4874
+ function isOrderedListItemMarker(itemMarker) {
4875
+ return itemMarker.kind === "." || itemMarker.kind === ")";
4876
+ }
4877
+ var ordered_list_marker_style_default = createRule("ordered-list-marker-style", {
4878
+ meta: {
4879
+ type: "layout",
4880
+ docs: {
4881
+ description: "enforce consistent ordered list marker style",
4882
+ categories: [],
4883
+ listCategory: "Stylistic"
4884
+ },
4885
+ fixable: "code",
4886
+ hasSuggestions: false,
4887
+ schema: [{
4888
+ type: "object",
4889
+ properties: {
4890
+ prefer: { enum: MARKERS },
4891
+ overrides: {
4892
+ type: "array",
4893
+ items: {
4894
+ type: "object",
4895
+ properties: {
4896
+ level: {
4897
+ type: "integer",
4898
+ minimum: 1
4899
+ },
4900
+ parentMarker: { enum: [
4901
+ ...MARKERS,
4902
+ "any",
4903
+ "bullet"
4904
+ ] },
4905
+ prefer: { enum: MARKERS }
4906
+ },
4907
+ additionalProperties: false
4908
+ }
4909
+ }
4910
+ },
4911
+ additionalProperties: false
4912
+ }],
4913
+ messages: { unexpected: "Ordered list marker should be '{{sequence}}{{markerKind}}'." }
4914
+ },
4915
+ create(context) {
4916
+ const sourceCode = context.sourceCode;
4917
+ const options = parseOptions(context.options[0] || {});
4918
+ let containerStack = {
4919
+ node: sourceCode.ast,
4920
+ level: 1,
4921
+ upper: null
4922
+ };
4923
+ /**
4924
+ * Check ordered list marker style
4925
+ */
4926
+ function checkOrderedList(node) {
4927
+ let parentMarker;
4928
+ if (containerStack.node.type === "listItem") {
4929
+ const parentMarkerKind = getListItemMarker(sourceCode, containerStack.node).kind;
4930
+ parentMarker = parentMarkerKind === "-" || parentMarkerKind === "*" || parentMarkerKind === "+" ? "bullet" : parentMarkerKind;
4931
+ } else parentMarker = "top";
4932
+ const { prefer } = options.get(containerStack.level, parentMarker);
4933
+ const nodeIndex = containerStack.node.children.indexOf(node);
4934
+ if (nodeIndex === -1) return;
4935
+ const prevNode = nodeIndex > 0 ? containerStack.node.children[nodeIndex - 1] : null;
4936
+ const prevBulletList = prevNode && (prevNode.type === "list" ? prevNode : null);
4937
+ const prevBulletListMarker = prevBulletList && getListItemMarker(sourceCode, prevBulletList);
4938
+ const expectedMarker = prevBulletListMarker?.kind !== prefer ? prefer : getOtherMarkerKind(prefer);
4939
+ const marker = getListItemMarker(sourceCode, node);
4940
+ if (marker.kind === expectedMarker || !isOrderedListItemMarker(marker)) return;
4941
+ const loc = sourceCode.getLoc(node);
4942
+ context.report({
4943
+ node,
4944
+ loc: {
4945
+ start: loc.start,
4946
+ end: {
4947
+ line: loc.start.line,
4948
+ column: loc.start.column + marker.raw.length
4949
+ }
4950
+ },
4951
+ messageId: "unexpected",
4952
+ data: {
4953
+ sequence: marker.sequence.raw,
4954
+ markerKind: expectedMarker
4955
+ },
4956
+ *fix(fixer) {
4957
+ if (prevNode?.type === "list" && prevBulletListMarker && isOrderedListItemMarker(prevBulletListMarker)) yield* fixMarker(prevNode.children[0], prevBulletListMarker.kind);
4958
+ yield* fixMarkers(node, expectedMarker);
4959
+ let prevMarker = expectedMarker;
4960
+ for (let index = nodeIndex + 1; index < containerStack.node.children.length; index++) {
4961
+ const nextNode = containerStack.node.children[index];
4962
+ if (nextNode.type !== "list") break;
4963
+ const nextMarker = getListItemMarker(sourceCode, nextNode);
4964
+ if (nextMarker.kind !== prevMarker) break;
4965
+ const expectedNextMarker = prevMarker === prefer ? getOtherMarkerKind(prefer) : prefer;
4966
+ yield* fixMarkers(nextNode, expectedNextMarker);
4967
+ prevMarker = expectedNextMarker;
4968
+ }
4969
+ /**
4970
+ * Fix ordered list markers
4971
+ */
4972
+ function* fixMarkers(list, replacementMarker) {
4973
+ for (const item of list.children) yield* fixMarker(item, replacementMarker);
4974
+ }
4975
+ /**
4976
+ * Fix ordered list item marker
4977
+ */
4978
+ function* fixMarker(item, replacementMarker) {
4979
+ const range = sourceCode.getRange(item);
4980
+ const itemMarker = getListItemMarker(sourceCode, item);
4981
+ if (!isOrderedListItemMarker(itemMarker)) return;
4982
+ yield fixer.replaceTextRange([range[0] + itemMarker.raw.length - 1, range[0] + itemMarker.raw.length], replacementMarker);
4983
+ }
4984
+ }
4985
+ });
4986
+ }
4987
+ return {
4988
+ list(node) {
4989
+ if (!node.ordered) return;
4990
+ checkOrderedList(node);
4991
+ },
4992
+ "root, blockquote, listItem, footnoteDefinition"(node) {
4993
+ containerStack = {
4994
+ node,
4995
+ level: node.type === "listItem" ? containerStack.level + 1 : 1,
4996
+ upper: containerStack
4997
+ };
4998
+ },
4999
+ "root, blockquote, listItem, footnoteDefinition:exit"() {
5000
+ containerStack = containerStack.upper;
5001
+ }
5002
+ };
5003
+ }
5004
+ });
5005
+
4309
5006
  //#endregion
4310
5007
  //#region src/rules/padding-line-between-blocks.ts
4311
5008
  /**
@@ -5123,122 +5820,6 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
5123
5820
  }
5124
5821
  });
5125
5822
 
5126
- //#endregion
5127
- //#region src/utils/setext-heading.ts
5128
- /**
5129
- * Parse the setext heading.
5130
- */
5131
- function parseSetextHeading(sourceCode, node) {
5132
- if (getHeadingKind(sourceCode, node) !== "setext") return null;
5133
- const lines = getParsedLines(sourceCode);
5134
- const contentLines = [];
5135
- const nodeLoc = sourceCode.getLoc(node);
5136
- for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
5137
- const content = parseContent(lines.get(lineNumber));
5138
- contentLines.push(content);
5139
- }
5140
- const underline = parseUnderline(lines.get(nodeLoc.end.line));
5141
- if (!underline) return null;
5142
- return {
5143
- contentLines,
5144
- underline
5145
- };
5146
- }
5147
- /**
5148
- * Parse the content line of a setext heading.
5149
- */
5150
- function parseContent(line) {
5151
- let prefix = "";
5152
- let spaceBefore = "";
5153
- let suffix = "";
5154
- for (let index = 0; index < line.text.length; index++) {
5155
- const c = line.text[index];
5156
- if (!c.trim()) {
5157
- spaceBefore += c;
5158
- continue;
5159
- }
5160
- if (c === ">" && spaceBefore.length < 4) {
5161
- prefix += spaceBefore + c;
5162
- spaceBefore = "";
5163
- continue;
5164
- }
5165
- suffix = line.text.slice(index);
5166
- break;
5167
- }
5168
- const content = suffix.trimEnd();
5169
- const spaceAfter = suffix.slice(content.length);
5170
- return {
5171
- text: content,
5172
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
5173
- loc: {
5174
- start: {
5175
- line: line.line,
5176
- column: prefix.length + spaceBefore.length + 1
5177
- },
5178
- end: {
5179
- line: line.line,
5180
- column: prefix.length + spaceBefore.length + content.length + 1
5181
- }
5182
- },
5183
- raws: {
5184
- prefix,
5185
- spaceBefore,
5186
- spaceAfter
5187
- }
5188
- };
5189
- }
5190
- /**
5191
- * Parse the underline of a setext heading.
5192
- */
5193
- function parseUnderline(line) {
5194
- let marker = null;
5195
- let underlineText = "";
5196
- let prefix = "";
5197
- let spaceBefore = "";
5198
- let spaceAfter = "";
5199
- for (let index = line.text.length - 1; index >= 0; index--) {
5200
- const c = line.text[index];
5201
- if (!marker) {
5202
- if (c === "=" || c === "-") {
5203
- underlineText = c + underlineText;
5204
- marker = c;
5205
- } else if (!c.trim()) spaceAfter = c + spaceAfter;
5206
- else return null;
5207
- continue;
5208
- }
5209
- if (c === marker) {
5210
- underlineText = c + spaceBefore + underlineText;
5211
- spaceBefore = "";
5212
- } else if (!c.trim()) spaceBefore = c + spaceBefore;
5213
- else {
5214
- prefix = line.text.slice(0, index + 1);
5215
- break;
5216
- }
5217
- }
5218
- if (!marker) return null;
5219
- const underlineLoc = {
5220
- start: {
5221
- line: line.line,
5222
- column: prefix.length + spaceBefore.length + 1
5223
- },
5224
- end: {
5225
- line: line.line,
5226
- column: prefix.length + spaceBefore.length + underlineText.length + 1
5227
- }
5228
- };
5229
- return {
5230
- text: underlineText,
5231
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
5232
- loc: underlineLoc,
5233
- marker,
5234
- raws: {
5235
- prefix,
5236
- spaceBefore,
5237
- spaceAfter
5238
- }
5239
- };
5240
- }
5241
-
5242
5823
  //#endregion
5243
5824
  //#region src/rules/setext-heading-underline-length.ts
5244
5825
  var setext_heading_underline_length_default = createRule("setext-heading-underline-length", {
@@ -6108,14 +6689,17 @@ var thematic_break_sequence_pattern_default = createRule("thematic-break-sequenc
6108
6689
  //#endregion
6109
6690
  //#region src/utils/rules.ts
6110
6691
  const rules$1 = [
6111
- atx_headings_closing_sequence_length_default,
6112
- atx_headings_closing_sequence_default,
6692
+ atx_heading_closing_sequence_length_default,
6693
+ atx_heading_closing_sequence_default,
6113
6694
  blockquote_marker_alignment_default,
6695
+ bullet_list_marker_style_default,
6114
6696
  canonical_code_block_language_default,
6115
6697
  definitions_last_default,
6116
6698
  emoji_notation_default,
6117
6699
  hard_linebreak_style_default,
6118
6700
  heading_casing_default,
6701
+ level1_heading_style_default,
6702
+ level2_heading_style_default,
6119
6703
  list_marker_alignment_default,
6120
6704
  no_laziness_blockquotes_default,
6121
6705
  no_multiple_empty_lines_default,
@@ -6123,6 +6707,7 @@ const rules$1 = [
6123
6707
  no_trailing_spaces_default,
6124
6708
  ordered_list_marker_sequence_default,
6125
6709
  ordered_list_marker_start_default,
6710
+ ordered_list_marker_style_default,
6126
6711
  padding_line_between_blocks_default,
6127
6712
  prefer_autolinks_default,
6128
6713
  prefer_fenced_code_blocks_default,
@@ -6177,7 +6762,7 @@ __export(meta_exports, {
6177
6762
  version: () => version
6178
6763
  });
6179
6764
  const name = "eslint-plugin-markdown-preferences";
6180
- const version = "0.17.0";
6765
+ const version = "0.18.0";
6181
6766
 
6182
6767
  //#endregion
6183
6768
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {