eslint-markdown 0.3.0 → 0.5.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.
@@ -17,10 +17,12 @@ export default function all(plugin: ESLint.Plugin): {
17
17
  readonly 'md/allow-image-url': "error";
18
18
  readonly 'md/allow-link-url': "error";
19
19
  readonly 'md/code-lang-shorthand': "error";
20
+ readonly 'md/consistent-code-style': "error";
20
21
  readonly 'md/consistent-delete-style': "error";
21
22
  readonly 'md/consistent-emphasis-style': "error";
22
23
  readonly 'md/consistent-strong-style': "error";
23
24
  readonly 'md/consistent-thematic-break-style': "error";
25
+ readonly 'md/consistent-unordered-list-style': "error";
24
26
  readonly 'md/no-control-character': "error";
25
27
  readonly 'md/no-curly-quote': "error";
26
28
  readonly 'md/no-double-space': "error";
@@ -14,10 +14,12 @@ export default function stylistic(plugin: ESLint.Plugin): {
14
14
  };
15
15
  readonly language: "markdown/gfm";
16
16
  readonly rules: {
17
+ readonly 'md/consistent-code-style': "error";
17
18
  readonly 'md/consistent-delete-style': "error";
18
19
  readonly 'md/consistent-emphasis-style': "error";
19
20
  readonly 'md/consistent-strong-style': "error";
20
21
  readonly 'md/consistent-thematic-break-style': "error";
22
+ readonly 'md/consistent-unordered-list-style': "error";
21
23
  readonly 'md/no-tab': "error";
22
24
  };
23
25
  };
@@ -2,6 +2,11 @@
2
2
  * @fileoverview Define common types.
3
3
  */
4
4
  import type { MarkdownRuleDefinition, MarkdownRuleDefinitionTypeOptions } from '@eslint/markdown';
5
+ /**
6
+ * Represents a rule module with specific rule options and message IDs.
7
+ * @template RuleOptions The rule options.
8
+ * @template MessageIds The message IDs.
9
+ */
5
10
  export type RuleModule<RuleOptions extends MarkdownRuleDefinitionTypeOptions['RuleOptions'], MessageIds extends MarkdownRuleDefinitionTypeOptions['MessageIds']> = MarkdownRuleDefinition<{
6
11
  RuleOptions: RuleOptions;
7
12
  MessageIds: MessageIds;
@@ -0,0 +1,43 @@
1
+ declare const _default: {
2
+ meta: {
3
+ type: "layout";
4
+ docs: {
5
+ description: string;
6
+ url: string;
7
+ recommended: boolean;
8
+ stylistic: true;
9
+ };
10
+ schema: {
11
+ type: "object";
12
+ properties: {
13
+ style: {
14
+ enum: string[];
15
+ };
16
+ };
17
+ additionalProperties: false;
18
+ }[];
19
+ defaultOptions: [{
20
+ style: "consistent";
21
+ }];
22
+ messages: {
23
+ style: string;
24
+ };
25
+ language: string;
26
+ dialects: string[];
27
+ };
28
+ create(context: import("@eslint/core").RuleContext<{
29
+ LangOptions: import("@eslint/markdown").MarkdownLanguageOptions;
30
+ Code: import("@eslint/markdown").MarkdownSourceCode;
31
+ RuleOptions: RuleOptions;
32
+ Node: import("mdast").Node;
33
+ MessageIds: "style";
34
+ }>): {
35
+ code(node: import("mdast").Code): void;
36
+ };
37
+ };
38
+ export default _default;
39
+ export type CodeStyle = "indent" | "fence-backtick" | "fence-tilde";
40
+ export type RuleOptions = [{
41
+ style: "consistent" | CodeStyle;
42
+ }];
43
+ export type MessageIds = "style";
@@ -0,0 +1,47 @@
1
+ declare const _default: {
2
+ meta: {
3
+ type: "layout";
4
+ docs: {
5
+ description: string;
6
+ url: string;
7
+ recommended: boolean;
8
+ stylistic: true;
9
+ };
10
+ fixable: "code";
11
+ schema: {
12
+ type: "object";
13
+ properties: {
14
+ style: {
15
+ enum: string[];
16
+ };
17
+ };
18
+ additionalProperties: false;
19
+ }[];
20
+ defaultOptions: [{
21
+ style: "consistent";
22
+ }];
23
+ messages: {
24
+ style: string;
25
+ };
26
+ language: string;
27
+ dialects: string[];
28
+ };
29
+ create(context: import("@eslint/core").RuleContext<{
30
+ LangOptions: import("@eslint/markdown").MarkdownLanguageOptions;
31
+ Code: import("@eslint/markdown").MarkdownSourceCode;
32
+ RuleOptions: RuleOptions;
33
+ Node: import("mdast").Node;
34
+ MessageIds: "style";
35
+ }>): {
36
+ list(): void;
37
+ 'list[ordered=false] > listItem'(node: ListItem): void;
38
+ 'list:exit'(): void;
39
+ };
40
+ };
41
+ export default _default;
42
+ export type UnorderedListStyle = "*" | "+" | "-";
43
+ export type RuleOptions = [{
44
+ style: "consistent" | "sublist" | UnorderedListStyle;
45
+ }];
46
+ export type MessageIds = "style";
47
+ import type { ListItem } from 'mdast';
@@ -2,10 +2,12 @@ declare const _default: {
2
2
  'allow-image-url': import("../core/types.js").RuleModule<import("./allow-image-url.js").RuleOptions, import("./allow-image-url.js").MessageIds>;
3
3
  'allow-link-url': import("../core/types.js").RuleModule<import("./allow-link-url.js").RuleOptions, import("./allow-link-url.js").MessageIds>;
4
4
  'code-lang-shorthand': import("../core/types.js").RuleModule<import("./code-lang-shorthand.js").RuleOptions, "codeLangShorthand">;
5
+ 'consistent-code-style': import("../core/types.js").RuleModule<import("./consistent-code-style.js").RuleOptions, "style">;
5
6
  'consistent-delete-style': import("../core/types.js").RuleModule<import("./consistent-delete-style.js").RuleOptions, "style">;
6
7
  'consistent-emphasis-style': import("../core/types.js").RuleModule<import("./consistent-emphasis-style.js").RuleOptions, "style">;
7
8
  'consistent-strong-style': import("../core/types.js").RuleModule<import("./consistent-strong-style.js").RuleOptions, "style">;
8
9
  'consistent-thematic-break-style': import("../core/types.js").RuleModule<import("./consistent-thematic-break-style.js").RuleOptions, "style">;
10
+ 'consistent-unordered-list-style': import("../core/types.js").RuleModule<import("./consistent-unordered-list-style.js").RuleOptions, "style">;
9
11
  'no-control-character': import("../core/types.js").RuleModule<import("./no-control-character.js").RuleOptions, "noControlCharacter">;
10
12
  'no-curly-quote': import("../core/types.js").RuleModule<import("./no-curly-quote.js").RuleOptions, "noCurlyQuote">;
11
13
  'no-double-space': import("../core/types.js").RuleModule<import("./no-double-space.js").RuleOptions, import("./no-double-space.js").MessageIds>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-markdown",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Lint your Markdown with ESLint.🛠️",
6
6
  "exports": {
@@ -41,10 +41,12 @@ export default function all(plugin) {
41
41
  'md/allow-image-url': 'error',
42
42
  'md/allow-link-url': 'error',
43
43
  'md/code-lang-shorthand': 'error',
44
+ 'md/consistent-code-style': 'error',
44
45
  'md/consistent-delete-style': 'error',
45
46
  'md/consistent-emphasis-style': 'error',
46
47
  'md/consistent-strong-style': 'error',
47
48
  'md/consistent-thematic-break-style': 'error',
49
+ 'md/consistent-unordered-list-style': 'error',
48
50
  'md/no-control-character': 'error',
49
51
  'md/no-curly-quote': 'error',
50
52
  'md/no-double-space': 'error',
@@ -38,10 +38,12 @@ export default function stylistic(plugin) {
38
38
  },
39
39
  language: 'markdown/gfm',
40
40
  rules: {
41
+ 'md/consistent-code-style': 'error',
41
42
  'md/consistent-delete-style': 'error',
42
43
  'md/consistent-emphasis-style': 'error',
43
44
  'md/consistent-strong-style': 'error',
44
45
  'md/consistent-thematic-break-style': 'error',
46
+ 'md/consistent-unordered-list-style': 'error',
45
47
  'md/no-tab': 'error',
46
48
  },
47
49
  });
@@ -76,7 +76,7 @@ export default function ruleTester(ruleName, rule, tests) {
76
76
 
77
77
  it('`meta.docs.description` should exist and follow the convention', () => {
78
78
  ok(meta?.docs?.description);
79
- match(meta?.docs?.description, /^(Enforce|Require|Disallow) .+[^. ]$/);
79
+ match(meta?.docs?.description, /^(?:Enforce|Require|Disallow) .+[^. ]$/);
80
80
  });
81
81
 
82
82
  it('`meta.docs.url` should exist and end with the rule name', () => {
package/src/core/types.ts CHANGED
@@ -15,6 +15,11 @@ import type {
15
15
  // Typedef
16
16
  // --------------------------------------------------------------------------------
17
17
 
18
+ /**
19
+ * Represents a rule module with specific rule options and message IDs.
20
+ * @template RuleOptions The rule options.
21
+ * @template MessageIds The message IDs.
22
+ */
18
23
  export type RuleModule<
19
24
  RuleOptions extends MarkdownRuleDefinitionTypeOptions['RuleOptions'],
20
25
  MessageIds extends MarkdownRuleDefinitionTypeOptions['MessageIds'],
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Rule to enforce consistent code style.
3
+ * @author 루밀LuMir(lumirlumir)
4
+ */
5
+
6
+ /*
7
+ * Note on autofix and suggestion safety:
8
+ * - Converting `fence-backtick` to `fence-tilde` is safe.
9
+ * - Converting `fence-backtick` to `indent` is not safe, as `lang` and `meta` information would be lost.
10
+ * - Converting `fence-tilde` to `fence-backtick` is safe.
11
+ * - Converting `fence-tilde` to `indent` is not safe, as `lang` and `meta` information would be lost.
12
+ * - Converting `indent` to `fence-backtick` is safe.
13
+ * - Converting `indent` to `fence-tilde` is safe.
14
+ */
15
+
16
+ // --------------------------------------------------------------------------------
17
+ // Import
18
+ // --------------------------------------------------------------------------------
19
+
20
+ import { URL_RULE_DOCS } from '../core/constants.js';
21
+
22
+ // --------------------------------------------------------------------------------
23
+ // Typedef
24
+ // --------------------------------------------------------------------------------
25
+
26
+ /**
27
+ * @import { RuleModule } from '../core/types.js';
28
+ * @typedef {'indent' | 'fence-backtick' | 'fence-tilde'} CodeStyle
29
+ * @typedef {[{ style: 'consistent' | CodeStyle }]} RuleOptions
30
+ * @typedef {'style'} MessageIds
31
+ */
32
+
33
+ // --------------------------------------------------------------------------------
34
+ // Helper
35
+ // --------------------------------------------------------------------------------
36
+
37
+ /**
38
+ * Get the current code style based on the given text.
39
+ * @param {string} text The text to determine the code style from.
40
+ * @returns {CodeStyle} The current code style.
41
+ */
42
+ function getCurrentCodeStyle(text) {
43
+ if (text === '`') {
44
+ return 'fence-backtick';
45
+ } else if (text === '~') {
46
+ return 'fence-tilde';
47
+ } else {
48
+ return 'indent';
49
+ }
50
+ }
51
+
52
+ // --------------------------------------------------------------------------------
53
+ // Rule Definition
54
+ // --------------------------------------------------------------------------------
55
+
56
+ /** @type {RuleModule<RuleOptions, MessageIds>} */
57
+ export default {
58
+ meta: {
59
+ type: 'layout',
60
+
61
+ docs: {
62
+ description: 'Enforce consistent code style',
63
+ url: URL_RULE_DOCS('consistent-code-style'),
64
+ recommended: false,
65
+ stylistic: true,
66
+ },
67
+
68
+ // fixable: 'code', // TODO
69
+
70
+ // hasSuggestions: true, // TODO
71
+
72
+ schema: [
73
+ {
74
+ type: 'object',
75
+ properties: {
76
+ style: {
77
+ enum: ['consistent', 'indent', 'fence-backtick', 'fence-tilde'],
78
+ },
79
+ },
80
+ additionalProperties: false,
81
+ },
82
+ ],
83
+
84
+ defaultOptions: [
85
+ {
86
+ style: 'consistent',
87
+ },
88
+ ],
89
+
90
+ messages: {
91
+ style: 'Code style should be `{{ style }}`.',
92
+ },
93
+
94
+ language: 'markdown',
95
+
96
+ dialects: ['commonmark', 'gfm'],
97
+ },
98
+
99
+ create(context) {
100
+ const { sourceCode } = context;
101
+ const [{ style }] = context.options;
102
+
103
+ /** @type {CodeStyle | null} */
104
+ let codeStyle = style === 'consistent' ? null : style;
105
+
106
+ return {
107
+ code(node) {
108
+ const [nodeStartOffset] = sourceCode.getRange(node);
109
+ const currentCodeStyle = getCurrentCodeStyle(sourceCode.text[nodeStartOffset]);
110
+
111
+ if (codeStyle === null) {
112
+ codeStyle = currentCodeStyle;
113
+ }
114
+
115
+ if (codeStyle !== currentCodeStyle) {
116
+ context.report({
117
+ node,
118
+
119
+ messageId: 'style',
120
+
121
+ data: {
122
+ style: codeStyle,
123
+ },
124
+ });
125
+ }
126
+ },
127
+ };
128
+ },
129
+ };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @fileoverview Rule to enforce consistent unordered list style.
3
+ * @author hyoban
4
+ */
5
+
6
+ // --------------------------------------------------------------------------------
7
+ // Import
8
+ // --------------------------------------------------------------------------------
9
+
10
+ import { URL_RULE_DOCS } from '../core/constants.js';
11
+
12
+ // --------------------------------------------------------------------------------
13
+ // Typedef
14
+ // --------------------------------------------------------------------------------
15
+
16
+ /**
17
+ * @import { ListItem } from 'mdast';
18
+ * @import { RuleModule } from '../core/types.js';
19
+ * @typedef {'*' | '+' | '-'} UnorderedListStyle
20
+ * @typedef {[{ style: 'consistent' | 'sublist' | UnorderedListStyle }]} RuleOptions
21
+ * @typedef {'style'} MessageIds
22
+ */
23
+
24
+ // --------------------------------------------------------------------------------
25
+ // Helper
26
+ // --------------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Get the next unordered list style in sequence.
30
+ * Inspired by [`markdownlint`](https://github.com/DavidAnson/markdownlint/blob/v0.40.0/lib/md004.mjs#L9).
31
+ * @param {UnorderedListStyle} currentUnorderedListStyle The current unordered list style.
32
+ * @returns {UnorderedListStyle} The next unordered list style.
33
+ */
34
+ function getNextUnorderedListStyle(currentUnorderedListStyle) {
35
+ if (currentUnorderedListStyle === '-') {
36
+ return '+';
37
+ } else if (currentUnorderedListStyle === '+') {
38
+ return '*';
39
+ } else {
40
+ return '-';
41
+ }
42
+ }
43
+
44
+ // --------------------------------------------------------------------------------
45
+ // Rule Definition
46
+ // --------------------------------------------------------------------------------
47
+
48
+ /** @type {RuleModule<RuleOptions, MessageIds>} */
49
+ export default {
50
+ meta: {
51
+ type: 'layout',
52
+
53
+ docs: {
54
+ description: 'Enforce consistent unordered list style',
55
+ url: URL_RULE_DOCS('consistent-unordered-list-style'),
56
+ recommended: false,
57
+ stylistic: true,
58
+ },
59
+
60
+ fixable: 'code',
61
+
62
+ schema: [
63
+ {
64
+ type: 'object',
65
+ properties: {
66
+ style: {
67
+ enum: ['consistent', 'sublist', '*', '+', '-'],
68
+ },
69
+ },
70
+ additionalProperties: false,
71
+ },
72
+ ],
73
+
74
+ defaultOptions: [
75
+ {
76
+ style: 'consistent',
77
+ },
78
+ ],
79
+
80
+ messages: {
81
+ style: 'Unordered list style should be `{{ style }}`.',
82
+ },
83
+
84
+ language: 'markdown',
85
+
86
+ dialects: ['commonmark', 'gfm'],
87
+ },
88
+
89
+ create(context) {
90
+ const { sourceCode } = context;
91
+ const [{ style }] = context.options;
92
+
93
+ /** @type {Array<UnorderedListStyle | null | undefined>} */
94
+ const unorderedListStyle = [
95
+ style === 'consistent' || style === 'sublist' ? null : style,
96
+ ];
97
+ let listDepth = -1;
98
+
99
+ return {
100
+ list() {
101
+ // When entering a `list` node, increase the depth.
102
+ // Counts both ordered and unordered lists, which matches `markdownlint`'s behavior.
103
+ listDepth++;
104
+ },
105
+
106
+ 'list[ordered=false] > listItem'(/** @type {ListItem} */ node) {
107
+ const [nodeStartOffset] = sourceCode.getRange(node);
108
+ const currentUnorderedListStyle = /** @type {UnorderedListStyle} */ (
109
+ sourceCode.text[nodeStartOffset]
110
+ );
111
+ const currentListDepth = style === 'sublist' ? listDepth : 0;
112
+
113
+ // `unorderedListStyle[currentListDepth]` can be `null` or `undefined`.
114
+ if (!unorderedListStyle[currentListDepth]) {
115
+ unorderedListStyle[currentListDepth] =
116
+ // If the previous depth used the same style, use the next style in sequence.
117
+ unorderedListStyle[currentListDepth - 1] === currentUnorderedListStyle
118
+ ? getNextUnorderedListStyle(currentUnorderedListStyle)
119
+ : currentUnorderedListStyle;
120
+ }
121
+
122
+ if (unorderedListStyle[currentListDepth] !== currentUnorderedListStyle) {
123
+ const stringifiedUnorderedListStyle = String(
124
+ unorderedListStyle[currentListDepth],
125
+ );
126
+
127
+ context.report({
128
+ loc: {
129
+ start: sourceCode.getLocFromIndex(nodeStartOffset),
130
+ end: sourceCode.getLocFromIndex(nodeStartOffset + 1),
131
+ },
132
+
133
+ messageId: 'style',
134
+
135
+ data: {
136
+ style: stringifiedUnorderedListStyle,
137
+ },
138
+
139
+ fix(fixer) {
140
+ return fixer.replaceTextRange(
141
+ [nodeStartOffset, nodeStartOffset + 1],
142
+ stringifiedUnorderedListStyle,
143
+ );
144
+ },
145
+ });
146
+ }
147
+ },
148
+
149
+ 'list:exit'() {
150
+ // When exiting a `list` node, decrease the depth.
151
+ // Counts both ordered and unordered lists, which matches `markdownlint`'s behavior.
152
+ listDepth--;
153
+ },
154
+ };
155
+ },
156
+ };
@@ -6,10 +6,12 @@
6
6
  import allowImageUrl from './allow-image-url.js';
7
7
  import allowLinkUrl from './allow-link-url.js';
8
8
  import codeLangShorthand from './code-lang-shorthand.js';
9
+ import consistentCodeStyle from './consistent-code-style.js';
9
10
  import consistentDeleteStyle from './consistent-delete-style.js';
10
11
  import consistentEmphasisStyle from './consistent-emphasis-style.js';
11
12
  import consistentStrongStyle from './consistent-strong-style.js';
12
13
  import consistentThematicBreakStyle from './consistent-thematic-break-style.js';
14
+ import consistentUnorderedListStyle from './consistent-unordered-list-style.js';
13
15
  // import enCapitalization from './en-capitalization.js';
14
16
  // import headingId from './heading-id.js';
15
17
  // import noBoldParagraph from './no-bold-paragraph.js';
@@ -30,10 +32,12 @@ export default {
30
32
  'allow-image-url': allowImageUrl,
31
33
  'allow-link-url': allowLinkUrl,
32
34
  'code-lang-shorthand': codeLangShorthand,
35
+ 'consistent-code-style': consistentCodeStyle,
33
36
  'consistent-delete-style': consistentDeleteStyle,
34
37
  'consistent-emphasis-style': consistentEmphasisStyle,
35
38
  'consistent-strong-style': consistentStrongStyle,
36
39
  'consistent-thematic-break-style': consistentThematicBreakStyle,
40
+ 'consistent-unordered-list-style': consistentUnorderedListStyle,
37
41
  // 'en-capitalization': enCapitalization,
38
42
  // 'heading-id': headingId,
39
43
  // 'no-bold-paragraph': noBoldParagraph,