eslint-markdown 0.18.1 → 0.19.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.
@@ -25,6 +25,7 @@ export default function all(plugin: ESLint.Plugin): {
25
25
  readonly 'md/consistent-emphasis-style': "error";
26
26
  readonly 'md/consistent-heading-style': "error";
27
27
  readonly 'md/consistent-inline-code-style': "error";
28
+ readonly 'md/consistent-ordered-list-style': "error";
28
29
  readonly 'md/consistent-strong-style': "error";
29
30
  readonly 'md/consistent-thematic-break-style': "error";
30
31
  readonly 'md/consistent-unordered-list-style': "error";
@@ -33,6 +33,7 @@ export default function all(plugin) {
33
33
  'md/consistent-emphasis-style': 'error',
34
34
  'md/consistent-heading-style': 'error',
35
35
  'md/consistent-inline-code-style': 'error',
36
+ 'md/consistent-ordered-list-style': 'error',
36
37
  'md/consistent-strong-style': 'error',
37
38
  'md/consistent-thematic-break-style': 'error',
38
39
  'md/consistent-unordered-list-style': 'error',
@@ -21,6 +21,7 @@ export default function stylistic(plugin: ESLint.Plugin): {
21
21
  readonly 'md/consistent-emphasis-style': "error";
22
22
  readonly 'md/consistent-heading-style': "error";
23
23
  readonly 'md/consistent-inline-code-style': "error";
24
+ readonly 'md/consistent-ordered-list-style': "error";
24
25
  readonly 'md/consistent-strong-style': "error";
25
26
  readonly 'md/consistent-thematic-break-style': "error";
26
27
  readonly 'md/consistent-unordered-list-style': "error";
@@ -29,6 +29,7 @@ export default function stylistic(plugin) {
29
29
  'md/consistent-emphasis-style': 'error',
30
30
  'md/consistent-heading-style': 'error',
31
31
  'md/consistent-inline-code-style': 'error',
32
+ 'md/consistent-ordered-list-style': 'error',
32
33
  'md/consistent-strong-style': 'error',
33
34
  'md/consistent-thematic-break-style': 'error',
34
35
  'md/consistent-unordered-list-style': 'error',
@@ -103,28 +103,30 @@ export default {
103
103
  const currentCodeStyle = getCodeStyle(currentCodeFenceChar);
104
104
  const nodeStartLineIndex = start.line - 1;
105
105
  const nodeEndLineIndex = end.line - 1;
106
+ const nodeStartLineText = lines[nodeStartLineIndex];
107
+ const nodeEndLineText = lines[nodeEndLineIndex];
108
+ const leadingCodeFenceLoc = {
109
+ start,
110
+ end: {
111
+ line: start.line,
112
+ column: (() => {
113
+ if (currentCodeStyle === 'indent') {
114
+ return nodeStartLineText.length + 1;
115
+ }
116
+ let { column } = start;
117
+ while (nodeStartLineText[column - 1] === currentCodeFenceChar) {
118
+ column++;
119
+ }
120
+ return column;
121
+ })(),
122
+ },
123
+ };
106
124
  if (codeStyle === null) {
107
125
  codeStyle = currentCodeStyle;
108
126
  }
109
127
  if (codeStyle !== currentCodeStyle) {
110
128
  context.report({
111
- loc: {
112
- start,
113
- end: {
114
- line: start.line,
115
- column: (() => {
116
- const nodeStartLineText = lines[nodeStartLineIndex];
117
- if (currentCodeStyle === 'indent') {
118
- return nodeStartLineText.length + 1;
119
- }
120
- let { column } = start;
121
- while (nodeStartLineText[column - 1] === currentCodeFenceChar) {
122
- column++;
123
- }
124
- return column;
125
- })(),
126
- },
127
- },
129
+ loc: leadingCodeFenceLoc,
128
130
  messageId: 'style',
129
131
  data: {
130
132
  style: codeStyle,
@@ -150,7 +152,7 @@ export default {
150
152
  continue;
151
153
  }
152
154
  context.report({
153
- node,
155
+ loc: leadingCodeFenceLoc,
154
156
  messageId: 'blankLineAbove',
155
157
  data: {
156
158
  blankLineAbove,
@@ -179,7 +181,18 @@ export default {
179
181
  continue;
180
182
  }
181
183
  context.report({
182
- node,
184
+ // Report the last line of the code block, as highlighting only the trailing
185
+ // code fence characters involves many edge cases that cannot be handled reliably.
186
+ loc: {
187
+ start: {
188
+ line: end.line,
189
+ column: 1,
190
+ },
191
+ end: {
192
+ line: end.line,
193
+ column: nodeEndLineText.length + 1,
194
+ },
195
+ },
183
196
  messageId: 'blankLineBelow',
184
197
  data: {
185
198
  blankLineBelow,
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview Rule to enforce consistent ordered list style.
3
+ * @author Ga eun Lee(tooth-is-silver)
4
+ */
5
+ import type { ListItem } from 'mdast';
6
+ type OrderedListStyle = (typeof ORDERED_LIST_STYLE)[number];
7
+ /**
8
+ * Options for the `consistent-ordered-list-style` rule.
9
+ */
10
+ type RuleOptions = [
11
+ {
12
+ /**
13
+ * When `style` is set to `'one-or-ordered'`, the rule allows either the `'one'` or `'ordered'` style based on the first two list item prefixes.
14
+ *
15
+ * When `style` is set to `'one'`, the rule requires every prefix to be `1`.
16
+ *
17
+ * When `style` is set to `'ordered'`, the rule requires prefixes to increase sequentially, starting from `1` or from `0` when the list has two or more items.
18
+ *
19
+ * When `style` is set to `'zero'`, the rule requires every prefix to be `0`.
20
+ * @default 'one-or-ordered'
21
+ */
22
+ style: 'one-or-ordered' | OrderedListStyle;
23
+ }
24
+ ];
25
+ declare const ORDERED_LIST_STYLE: readonly ["one", "ordered", "zero"];
26
+ declare const _default: {
27
+ readonly meta: {
28
+ readonly type: "layout";
29
+ readonly docs: {
30
+ readonly description: "Enforce consistent ordered list style";
31
+ readonly url: string;
32
+ readonly recommended: false;
33
+ readonly stylistic: true;
34
+ };
35
+ readonly schema: [{
36
+ readonly type: "object";
37
+ readonly properties: {
38
+ readonly style: {
39
+ readonly enum: ["one-or-ordered", "one", "ordered", "zero"];
40
+ };
41
+ };
42
+ readonly additionalProperties: false;
43
+ }];
44
+ readonly defaultOptions: [{
45
+ readonly style: "one-or-ordered";
46
+ }];
47
+ readonly messages: {
48
+ readonly style: "Ordered list style should be `{{ style }}`.";
49
+ };
50
+ readonly language: "markdown";
51
+ readonly dialects: ["commonmark", "gfm"];
52
+ };
53
+ readonly create: (context: import("../core/types.js").RuleContext<{
54
+ RuleOptions: RuleOptions;
55
+ MessageIds: "style";
56
+ }>) => {
57
+ 'list[ordered=true]'(): void;
58
+ 'list[ordered=true] > listItem'(node: ListItem): void;
59
+ 'list[ordered=true]:exit'(): void;
60
+ };
61
+ };
62
+ export default _default;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @fileoverview Rule to enforce consistent ordered list style.
3
+ * @author Ga eun Lee(tooth-is-silver)
4
+ */
5
+ import { URL_RULE_DOCS } from '../core/constants.js';
6
+ // --------------------------------------------------------------------------------
7
+ // Helper
8
+ // --------------------------------------------------------------------------------
9
+ const ORDERED_LIST_STYLE = ['one', 'ordered', 'zero'];
10
+ /**
11
+ * Ordered list start numbers must be nine digits or less.
12
+ * @see https://spec.commonmark.org/0.31.2/#example-265
13
+ */
14
+ const orderedListPrefixRegex = /^\d{1,9}/u;
15
+ // --------------------------------------------------------------------------------
16
+ // Rule Definition
17
+ // --------------------------------------------------------------------------------
18
+ export default {
19
+ meta: {
20
+ type: 'layout',
21
+ docs: {
22
+ description: 'Enforce consistent ordered list style',
23
+ url: URL_RULE_DOCS('consistent-ordered-list-style'),
24
+ recommended: false,
25
+ stylistic: true,
26
+ },
27
+ schema: [
28
+ {
29
+ type: 'object',
30
+ properties: {
31
+ style: {
32
+ enum: ['one-or-ordered', ...ORDERED_LIST_STYLE],
33
+ },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ ],
38
+ defaultOptions: [
39
+ {
40
+ style: 'one-or-ordered',
41
+ },
42
+ ],
43
+ messages: {
44
+ style: 'Ordered list style should be `{{ style }}`.',
45
+ },
46
+ language: 'markdown',
47
+ dialects: ['commonmark', 'gfm'],
48
+ },
49
+ create(context) {
50
+ const { sourceCode } = context;
51
+ const [{ style }] = context.options;
52
+ const orderedListPrefixesStack = [];
53
+ return {
54
+ 'list[ordered=true]'() {
55
+ orderedListPrefixesStack.push([]);
56
+ },
57
+ 'list[ordered=true] > listItem'(node) {
58
+ const [nodeStartOffset] = sourceCode.getRange(node);
59
+ // `match` is never `null` because this visitor only runs for
60
+ // ordered list items, whose source text starts with a numeric marker.
61
+ const match = orderedListPrefixRegex.exec(sourceCode.getText(node));
62
+ const orderedListPrefix = match[0];
63
+ orderedListPrefixesStack[orderedListPrefixesStack.length - 1].push({
64
+ startOffset: nodeStartOffset,
65
+ endOffset: nodeStartOffset + orderedListPrefix.length,
66
+ number: Number(orderedListPrefix),
67
+ });
68
+ },
69
+ 'list[ordered=true]:exit'() {
70
+ // Can never be `undefined` because `list[ordered=true]` always pushes to the stack before exiting.
71
+ const orderedListPrefixes = orderedListPrefixesStack.pop();
72
+ const [firstOrderedListPrefix, secondOrderedListPrefix] = orderedListPrefixes;
73
+ const hasMultipleItems = orderedListPrefixes.length > 1;
74
+ let orderedListStyle = style;
75
+ if (orderedListStyle === 'one-or-ordered') {
76
+ orderedListStyle =
77
+ hasMultipleItems &&
78
+ (firstOrderedListPrefix.number === 0 || secondOrderedListPrefix.number !== 1)
79
+ ? 'ordered'
80
+ : 'one';
81
+ }
82
+ let expectedOrderedListPrefixNumber = orderedListStyle === 'zero' ||
83
+ (orderedListStyle === 'ordered' &&
84
+ hasMultipleItems &&
85
+ firstOrderedListPrefix.number === 0)
86
+ ? 0
87
+ : 1;
88
+ for (const { startOffset, endOffset, number } of orderedListPrefixes) {
89
+ if (number !== expectedOrderedListPrefixNumber) {
90
+ context.report({
91
+ loc: {
92
+ start: sourceCode.getLocFromIndex(startOffset),
93
+ end: sourceCode.getLocFromIndex(endOffset),
94
+ },
95
+ messageId: 'style',
96
+ data: {
97
+ style: orderedListStyle,
98
+ },
99
+ });
100
+ }
101
+ if (orderedListStyle === 'ordered') {
102
+ expectedOrderedListPrefixNumber++;
103
+ }
104
+ }
105
+ },
106
+ };
107
+ },
108
+ };
@@ -632,6 +632,44 @@ declare const _default: {
632
632
  inlineCode(node: import("mdast").InlineCode): void;
633
633
  };
634
634
  };
635
+ 'consistent-ordered-list-style': {
636
+ readonly meta: {
637
+ readonly type: "layout";
638
+ readonly docs: {
639
+ readonly description: "Enforce consistent ordered list style";
640
+ readonly url: string;
641
+ readonly recommended: false;
642
+ readonly stylistic: true;
643
+ };
644
+ readonly schema: [{
645
+ readonly type: "object";
646
+ readonly properties: {
647
+ readonly style: {
648
+ readonly enum: ["one-or-ordered", "one", "ordered", "zero"];
649
+ };
650
+ };
651
+ readonly additionalProperties: false;
652
+ }];
653
+ readonly defaultOptions: [{
654
+ readonly style: "one-or-ordered";
655
+ }];
656
+ readonly messages: {
657
+ readonly style: "Ordered list style should be `{{ style }}`.";
658
+ };
659
+ readonly language: "markdown";
660
+ readonly dialects: ["commonmark", "gfm"];
661
+ };
662
+ readonly create: (context: import("../core/types.js").RuleContext<{
663
+ RuleOptions: [{
664
+ style: "one-or-ordered" | ("one" | "ordered" | "zero");
665
+ }];
666
+ MessageIds: "style";
667
+ }>) => {
668
+ 'list[ordered=true]'(): void;
669
+ 'list[ordered=true] > listItem'(node: import("mdast").ListItem): void;
670
+ 'list[ordered=true]:exit'(): void;
671
+ };
672
+ };
635
673
  'consistent-strong-style': {
636
674
  readonly meta: {
637
675
  readonly type: "layout";
@@ -838,6 +876,12 @@ declare const _default: {
838
876
  readonly skipInlineCode: {
839
877
  readonly type: "boolean";
840
878
  };
879
+ readonly skipMath: {
880
+ readonly type: "boolean";
881
+ };
882
+ readonly skipInlineMath: {
883
+ readonly type: "boolean";
884
+ };
841
885
  };
842
886
  readonly additionalProperties: false;
843
887
  }];
@@ -845,6 +889,8 @@ declare const _default: {
845
889
  readonly allow: [];
846
890
  readonly skipCode: true;
847
891
  readonly skipInlineCode: true;
892
+ readonly skipMath: false;
893
+ readonly skipInlineMath: false;
848
894
  }];
849
895
  readonly messages: {
850
896
  readonly noControlCharacter: "Control character `{{ controlCharacter }}` is not allowed.";
@@ -858,11 +904,15 @@ declare const _default: {
858
904
  allow: string[];
859
905
  skipCode: boolean | string[];
860
906
  skipInlineCode: boolean;
907
+ skipMath: boolean;
908
+ skipInlineMath: boolean;
861
909
  }];
862
910
  MessageIds: "noControlCharacter" | "suggestRemove";
863
911
  }>) => {
864
912
  code(node: import("mdast").Code): void;
865
913
  inlineCode(node: import("mdast").InlineCode): void;
914
+ math(node: import("mdast-util-math").Math): void;
915
+ inlineMath(node: import("mdast-util-math").InlineMath): void;
866
916
  'root:exit'(): void;
867
917
  };
868
918
  };
@@ -9,6 +9,7 @@ import consistentDeleteStyle from './consistent-delete-style.js';
9
9
  import consistentEmphasisStyle from './consistent-emphasis-style.js';
10
10
  import consistentHeadingStyle from './consistent-heading-style.js';
11
11
  import consistentInlineCodeStyle from './consistent-inline-code-style.js';
12
+ import consistentOrderedListStyle from './consistent-ordered-list-style.js';
12
13
  import consistentStrongStyle from './consistent-strong-style.js';
13
14
  import consistentThematicBreakStyle from './consistent-thematic-break-style.js';
14
15
  import consistentUnorderedListStyle from './consistent-unordered-list-style.js';
@@ -40,6 +41,7 @@ export default {
40
41
  'consistent-emphasis-style': consistentEmphasisStyle,
41
42
  'consistent-heading-style': consistentHeadingStyle,
42
43
  'consistent-inline-code-style': consistentInlineCodeStyle,
44
+ 'consistent-ordered-list-style': consistentOrderedListStyle,
43
45
  'consistent-strong-style': consistentStrongStyle,
44
46
  'consistent-thematic-break-style': consistentThematicBreakStyle,
45
47
  'consistent-unordered-list-style': consistentUnorderedListStyle,
@@ -24,6 +24,16 @@ type RuleOptions = [
24
24
  * @default true
25
25
  */
26
26
  skipInlineCode: boolean;
27
+ /**
28
+ * `true` allows control characters in all math blocks.
29
+ * @default false
30
+ */
31
+ skipMath: boolean;
32
+ /**
33
+ * `true` allows control characters in all inline math.
34
+ * @default false
35
+ */
36
+ skipInlineMath: boolean;
27
37
  }
28
38
  ];
29
39
  type MessageIds = 'noControlCharacter' | 'suggestRemove';
@@ -61,6 +71,12 @@ declare const _default: {
61
71
  readonly skipInlineCode: {
62
72
  readonly type: "boolean";
63
73
  };
74
+ readonly skipMath: {
75
+ readonly type: "boolean";
76
+ };
77
+ readonly skipInlineMath: {
78
+ readonly type: "boolean";
79
+ };
64
80
  };
65
81
  readonly additionalProperties: false;
66
82
  }];
@@ -68,6 +84,8 @@ declare const _default: {
68
84
  readonly allow: [];
69
85
  readonly skipCode: true;
70
86
  readonly skipInlineCode: true;
87
+ readonly skipMath: false;
88
+ readonly skipInlineMath: false;
71
89
  }];
72
90
  readonly messages: {
73
91
  readonly noControlCharacter: "Control character `{{ controlCharacter }}` is not allowed.";
@@ -82,6 +100,8 @@ declare const _default: {
82
100
  }>) => {
83
101
  code(node: import("mdast").Code): void;
84
102
  inlineCode(node: import("mdast").InlineCode): void;
103
+ math(node: import("mdast-util-math").Math): void;
104
+ inlineMath(node: import("mdast-util-math").InlineMath): void;
85
105
  'root:exit'(): void;
86
106
  };
87
107
  };
@@ -53,6 +53,12 @@ export default {
53
53
  skipInlineCode: {
54
54
  type: 'boolean',
55
55
  },
56
+ skipMath: {
57
+ type: 'boolean',
58
+ },
59
+ skipInlineMath: {
60
+ type: 'boolean',
61
+ },
56
62
  },
57
63
  additionalProperties: false,
58
64
  },
@@ -62,6 +68,8 @@ export default {
62
68
  allow: [],
63
69
  skipCode: true,
64
70
  skipInlineCode: true,
71
+ skipMath: false,
72
+ skipInlineMath: false,
65
73
  },
66
74
  ],
67
75
  messages: {
@@ -73,7 +81,7 @@ export default {
73
81
  },
74
82
  create(context) {
75
83
  const { sourceCode } = context;
76
- const [{ allow, skipCode, skipInlineCode }] = context.options;
84
+ const [{ allow, skipCode, skipInlineCode, skipMath, skipInlineMath }] = context.options;
77
85
  const skipRanges = new SkipRanges();
78
86
  return {
79
87
  code(node) {
@@ -84,6 +92,14 @@ export default {
84
92
  if (skipInlineCode)
85
93
  skipRanges.push(sourceCode.getRange(node)); // Store range information of `InlineCode`.
86
94
  },
95
+ math(node) {
96
+ if (skipMath)
97
+ skipRanges.push(sourceCode.getRange(node)); // Store range information of `Math`.
98
+ },
99
+ inlineMath(node) {
100
+ if (skipInlineMath)
101
+ skipRanges.push(sourceCode.getRange(node)); // Store range information of `InlineMath`.
102
+ },
87
103
  'root:exit'() {
88
104
  const matches = sourceCode.text.matchAll(controlCharacterRegex);
89
105
  for (const match of matches) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-markdown",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Lint your Markdown with ESLint. Additional rules for use with `@eslint/markdown`.🛠️",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "scripts": {
59
59
  "prepublishOnly": "npm run build",
60
- "build": "tsc -b tsconfig.build.json && node ../../scripts/build-config.js && tsc -b tsconfig.build.json && node ../../scripts/cp.js ../../LICENSE.md LICENSE.md ../../README.md README.md",
60
+ "build": "tsc -b tsconfig.build.json && node ../../scripts/build-config.js && tsc -b tsconfig.build.json && node ../../scripts/cp.js ../../README.md README.md",
61
61
  "test": "npm run test:types && npm run test:unit",
62
62
  "test:types": "tsc -b ./tsconfig.test-d.json",
63
63
  "test:unit": "vitest run"