eslint-markdown 0.17.1 → 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.
@@ -37,6 +37,7 @@ export default function all(plugin: ESLint.Plugin): {
37
37
  readonly 'md/no-git-conflict-marker': "error";
38
38
  readonly 'md/no-irregular-dash': "error";
39
39
  readonly 'md/no-irregular-whitespace': "error";
40
+ readonly 'md/no-multiple-atx-heading-space': "error";
40
41
  readonly 'md/no-shell-dollar': "error";
41
42
  readonly 'md/no-tab': "error";
42
43
  readonly 'md/no-trailing-heading-punctuation': "error";
@@ -45,6 +45,7 @@ export default function all(plugin) {
45
45
  'md/no-git-conflict-marker': 'error',
46
46
  'md/no-irregular-dash': 'error',
47
47
  'md/no-irregular-whitespace': 'error',
48
+ 'md/no-multiple-atx-heading-space': 'error',
48
49
  'md/no-shell-dollar': 'error',
49
50
  'md/no-tab': 'error',
50
51
  'md/no-trailing-heading-punctuation': 'error',
@@ -25,6 +25,7 @@ export default function stylistic(plugin: ESLint.Plugin): {
25
25
  readonly 'md/consistent-thematic-break-style': "error";
26
26
  readonly 'md/consistent-unordered-list-style': "error";
27
27
  readonly 'md/no-consecutive-blank-line': "error";
28
+ readonly 'md/no-multiple-atx-heading-space': "error";
28
29
  readonly 'md/no-tab': "error";
29
30
  readonly 'md/no-trailing-heading-punctuation': "error";
30
31
  };
@@ -33,6 +33,7 @@ export default function stylistic(plugin) {
33
33
  'md/consistent-thematic-break-style': 'error',
34
34
  'md/consistent-unordered-list-style': 'error',
35
35
  'md/no-consecutive-blank-line': 'error',
36
+ 'md/no-multiple-atx-heading-space': 'error',
36
37
  'md/no-tab': 'error',
37
38
  'md/no-trailing-heading-punctuation': 'error',
38
39
  },
@@ -40,3 +40,8 @@ export declare const escapedTrailingBackslashRegex: RegExp;
40
40
  * @see https://github.com/DavidAnson/markdownlint/blob/v0.41.1/helpers/helpers.cjs#L36-L38
41
41
  */
42
42
  export declare const gemojiRegex: RegExp;
43
+ /**
44
+ * Matches the closing sequence of a closed ATX heading.
45
+ * @see https://spec.commonmark.org/0.31.2/#atx-headings
46
+ */
47
+ export declare const trailingAtxHeadingHashRegex: RegExp;
@@ -59,5 +59,10 @@ export const escapedTrailingBackslashRegex = /(?<=(?<!\\)(?:\\{2})*)\\$/u;
59
59
  * @see https://github.com/DavidAnson/markdownlint/blob/v0.41.1/helpers/helpers.cjs#L36-L38
60
60
  */
61
61
  export const gemojiRegex = /:(?:[abmovx]|[-+]1|100|1234|(?:1st|2nd|3rd)_place_medal|8ball|clock\d{1,4}|e-mail|non-potable_water|o2|t-rex|u5272|u5408|u55b6|u6307|u6708|u6709|u6e80|u7121|u7533|u7981|u7a7a|[a-z]{2,15}2?|[a-z]{1,14}(?:_[a-z\d]{1,16})+):/;
62
+ /**
63
+ * Matches the closing sequence of a closed ATX heading.
64
+ * @see https://spec.commonmark.org/0.31.2/#atx-headings
65
+ */
66
+ export const trailingAtxHeadingHashRegex = /[ \t]#+[ \t]*$/;
62
67
  // #endregion regex
63
68
  // --------------------------------------------------------------------------------
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @fileoverview Get the style of a Markdown heading.
3
+ */
4
+ import type { MarkdownSourceCode } from '@eslint/markdown';
5
+ import type { Heading } from 'mdast';
6
+ export type HeadingStyle = (typeof HEADING_STYLE)[number];
7
+ export declare const HEADING_STYLE: readonly ["atx", "atx-closed", "setext"];
8
+ /**
9
+ * Gets the style of a Markdown heading.
10
+ * @param node The heading node.
11
+ * @param sourceCode The Markdown source code.
12
+ * @returns The heading style.
13
+ */
14
+ export declare function getHeadingStyle(node: Heading, sourceCode: MarkdownSourceCode): HeadingStyle;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview Get the style of a Markdown heading.
3
+ */
4
+ import { trailingAtxHeadingHashRegex } from '../constants.js';
5
+ // --------------------------------------------------------------------------------
6
+ // Export
7
+ // --------------------------------------------------------------------------------
8
+ export const HEADING_STYLE = ['atx', 'atx-closed', 'setext'];
9
+ /**
10
+ * Gets the style of a Markdown heading.
11
+ * @param node The heading node.
12
+ * @param sourceCode The Markdown source code.
13
+ * @returns The heading style.
14
+ */
15
+ export function getHeadingStyle(node, sourceCode) {
16
+ const { start, end } = sourceCode.getLoc(node);
17
+ if (start.line !== end.line /* Multiline Heading */) {
18
+ return 'setext';
19
+ }
20
+ else if (trailingAtxHeadingHashRegex.test(sourceCode.getText(node))) {
21
+ return 'atx-closed';
22
+ }
23
+ else {
24
+ return 'atx';
25
+ }
26
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './escape-string-regexp.js';
2
2
  export * from './get-code-style.js';
3
+ export * from './get-heading-style.js';
3
4
  export * from './html.js';
4
5
  export * from './is-blank-line.js';
5
6
  export * from './normalize-regex-pattern.js';
@@ -1,5 +1,6 @@
1
1
  export * from './escape-string-regexp.js';
2
2
  export * from './get-code-style.js';
3
+ export * from './get-heading-style.js';
3
4
  export * from './html.js';
4
5
  export * from './is-blank-line.js';
5
6
  export * from './normalize-regex-pattern.js';
@@ -2,7 +2,6 @@
2
2
  * @fileoverview Rule to enforce consistent code style.
3
3
  * @author lumir(lumirlumir)
4
4
  */
5
- import { type CodeStyle } from '../core/utils/index.js';
6
5
  /**
7
6
  * Options for the `consistent-code-style` rule.
8
7
  */
@@ -12,7 +11,7 @@ type RuleOptions = [
12
11
  * When `style` is set to `'consistent'`, the rule enforces that all code blocks in the document use the same style as the first one encountered.
13
12
  * @default 'consistent'
14
13
  */
15
- style: 'consistent' | CodeStyle;
14
+ style: (typeof EXTENDED_CODE_STYLE)[number];
16
15
  /**
17
16
  * Require a specific number of blank lines above each fenced code block.
18
17
  * @default false
@@ -26,6 +25,7 @@ type RuleOptions = [
26
25
  }
27
26
  ];
28
27
  type MessageIds = 'style' | 'blankLineAbove' | 'blankLineBelow';
28
+ declare const EXTENDED_CODE_STYLE: readonly ["consistent", "indent", "fence-backtick", "fence-tilde"];
29
29
  declare const _default: {
30
30
  readonly meta: {
31
31
  readonly type: "layout";
@@ -39,7 +39,7 @@ declare const _default: {
39
39
  readonly type: "object";
40
40
  readonly properties: {
41
41
  readonly style: {
42
- readonly enum: ["consistent", "indent", "fence-backtick", "fence-tilde"];
42
+ readonly enum: readonly ["consistent", "indent", "fence-backtick", "fence-tilde"];
43
43
  };
44
44
  readonly blankLineAbove: {
45
45
  readonly oneOf: [{
@@ -17,6 +17,10 @@
17
17
  import { CODE_STYLE, getCodeStyle, isBlankLine, } from '../core/utils/index.js';
18
18
  import { URL_RULE_DOCS } from '../core/constants.js';
19
19
  // --------------------------------------------------------------------------------
20
+ // Helper
21
+ // --------------------------------------------------------------------------------
22
+ const EXTENDED_CODE_STYLE = ['consistent', ...CODE_STYLE];
23
+ // --------------------------------------------------------------------------------
20
24
  // Rule Definition
21
25
  // --------------------------------------------------------------------------------
22
26
  export default {
@@ -35,7 +39,7 @@ export default {
35
39
  type: 'object',
36
40
  properties: {
37
41
  style: {
38
- enum: ['consistent', ...CODE_STYLE],
42
+ enum: EXTENDED_CODE_STYLE,
39
43
  },
40
44
  blankLineAbove: {
41
45
  oneOf: [
@@ -5,12 +5,25 @@
5
5
  * @see https://github.com/DavidAnson/markdownlint/blob/v0.41.1/lib/md003.mjs
6
6
  */
7
7
  import type { Heading } from 'mdast';
8
- type HeadingStyle = (typeof HEADING_STYLE)[number];
9
- type RuleOptions = [{
10
- style: HeadingStyle;
11
- }];
8
+ type RuleOptions = [
9
+ {
10
+ /**
11
+ * When `style` is set to `'consistent'`, the rule enforces that all headings in the document use the same style as the first one encountered.
12
+ *
13
+ * You can also specify a particular style by setting `style` to one of the following values:
14
+ *
15
+ * - `'atx'`: Require ATX headings at every level, such as `## Heading`.
16
+ * - `'atx-closed'`: Require closed ATX headings at every level, such as `## Heading ##`.
17
+ * - `'setext'`: Require Setext headings at levels 1 and 2. Headings at levels 3 through 6 are reported because Setext does not support those levels.
18
+ * - `'setext-with-atx'`: Require Setext headings at levels 1 and 2, and ATX headings at levels 3 through 6.
19
+ * - `'setext-with-atx-closed'`: Require Setext headings at levels 1 and 2, and closed ATX headings at levels 3 through 6.
20
+ * @default 'consistent'
21
+ */
22
+ style: (typeof EXTENDED_HEADING_STYLE)[number];
23
+ }
24
+ ];
12
25
  type MessageIds = 'style' | 'suggestAtxToSetext' | 'suggestAtxClosedToSetext';
13
- declare const HEADING_STYLE: readonly ["consistent", "atx", "atx-closed", "setext", "setext-with-atx", "setext-with-atx-closed"];
26
+ declare const EXTENDED_HEADING_STYLE: readonly ["consistent", "atx", "atx-closed", "setext", "setext-with-atx", "setext-with-atx-closed"];
14
27
  declare const _default: {
15
28
  readonly meta: {
16
29
  readonly type: "layout";
@@ -4,24 +4,18 @@
4
4
  * @author lumir(lumirlumir)
5
5
  * @see https://github.com/DavidAnson/markdownlint/blob/v0.41.1/lib/md003.mjs
6
6
  */
7
- import { URL_RULE_DOCS } from '../core/constants.js';
7
+ import { HEADING_STYLE, getHeadingStyle } from '../core/utils/index.js';
8
+ import { URL_RULE_DOCS, trailingAtxHeadingHashRegex } from '../core/constants.js';
8
9
  // --------------------------------------------------------------------------------
9
10
  // Helper
10
11
  // --------------------------------------------------------------------------------
11
12
  const SETEXT_MAX_DEPTH = 2;
12
- const HEADING_STYLE = [
13
+ const EXTENDED_HEADING_STYLE = [
13
14
  'consistent',
14
- 'atx',
15
- 'atx-closed',
16
- 'setext',
15
+ ...HEADING_STYLE,
17
16
  'setext-with-atx',
18
17
  'setext-with-atx-closed',
19
18
  ];
20
- /**
21
- * Matches the closing sequence of a closed ATX heading.
22
- * @see https://spec.commonmark.org/0.31.2/#atx-headings
23
- */
24
- const trailingAtxHeadingHashRegex = /[ \t]#+[ \t]*$/;
25
19
  /**
26
20
  * Returns the setext marker for the given heading depth.
27
21
  * @param depth The depth of the heading (`1` or `2`).
@@ -49,7 +43,7 @@ export default {
49
43
  type: 'object',
50
44
  properties: {
51
45
  style: {
52
- enum: HEADING_STYLE,
46
+ enum: EXTENDED_HEADING_STYLE,
53
47
  },
54
48
  },
55
49
  additionalProperties: false,
@@ -88,16 +82,7 @@ export default {
88
82
  return {
89
83
  // The `heading` selector is more general, so it is visited before the other `heading[xxx]` selectors.
90
84
  heading(node) {
91
- const { start, end } = sourceCode.getLoc(node);
92
- if (start.line !== end.line /* Multiline Heading */) {
93
- currentHeadingStyle = 'setext';
94
- }
95
- else if (trailingAtxHeadingHashRegex.test(sourceCode.getText(node))) {
96
- currentHeadingStyle = 'atx-closed';
97
- }
98
- else {
99
- currentHeadingStyle = 'atx';
100
- }
85
+ currentHeadingStyle = getHeadingStyle(node, sourceCode);
101
86
  if (headingStyle === null) {
102
87
  headingStyle = currentHeadingStyle;
103
88
  }
@@ -445,7 +445,7 @@ declare const _default: {
445
445
  readonly type: "object";
446
446
  readonly properties: {
447
447
  readonly style: {
448
- readonly enum: ["consistent", "indent", "fence-backtick", "fence-tilde"];
448
+ readonly enum: readonly ["consistent", "indent", "fence-backtick", "fence-tilde"];
449
449
  };
450
450
  readonly blankLineAbove: {
451
451
  readonly oneOf: [{
@@ -481,7 +481,7 @@ declare const _default: {
481
481
  };
482
482
  readonly create: (context: import("../core/types.js").RuleContext<{
483
483
  RuleOptions: [{
484
- style: "consistent" | import("../core/utils/get-code-style.js").CodeStyle;
484
+ style: "indent" | "fence-backtick" | "fence-tilde" | "consistent";
485
485
  blankLineAbove: number | false;
486
486
  blankLineBelow: number | false;
487
487
  }];
@@ -599,7 +599,7 @@ declare const _default: {
599
599
  };
600
600
  readonly create: (context: import("../core/types.js").RuleContext<{
601
601
  RuleOptions: [{
602
- style: "consistent" | "atx" | "atx-closed" | "setext" | "setext-with-atx" | "setext-with-atx-closed";
602
+ style: "atx" | "atx-closed" | "setext" | "consistent" | "setext-with-atx" | "setext-with-atx-closed";
603
603
  }];
604
604
  MessageIds: "style" | "suggestAtxToSetext" | "suggestAtxClosedToSetext";
605
605
  }>) => {
@@ -774,12 +774,16 @@ declare const _default: {
774
774
  readonly uniqueItems: true;
775
775
  }];
776
776
  };
777
+ readonly skipMath: {
778
+ readonly type: "boolean";
779
+ };
777
780
  };
778
781
  readonly additionalProperties: false;
779
782
  }];
780
783
  readonly defaultOptions: [{
781
784
  readonly max: 1;
782
785
  readonly skipCode: true;
786
+ readonly skipMath: true;
783
787
  }];
784
788
  readonly messages: {
785
789
  readonly noConsecutiveBlankLine: "More than {{ max }} consecutive blank line(s) are not allowed.";
@@ -791,10 +795,12 @@ declare const _default: {
791
795
  RuleOptions: [{
792
796
  max: number;
793
797
  skipCode: boolean | string[];
798
+ skipMath: boolean;
794
799
  }];
795
800
  MessageIds: "noConsecutiveBlankLine";
796
801
  }>) => {
797
802
  code(node: import("mdast").Code): void;
803
+ math(node: import("mdast-util-math").Math): void;
798
804
  'root:exit'(): void;
799
805
  };
800
806
  };
@@ -1016,11 +1022,15 @@ declare const _default: {
1016
1022
  };
1017
1023
  readonly uniqueItems: true;
1018
1024
  };
1025
+ readonly style: {
1026
+ readonly enum: ["emoji", "gemoji"];
1027
+ };
1019
1028
  };
1020
1029
  readonly additionalProperties: false;
1021
1030
  }];
1022
1031
  readonly defaultOptions: [{
1023
1032
  readonly allow: [];
1033
+ readonly style: "emoji";
1024
1034
  }];
1025
1035
  readonly messages: {
1026
1036
  readonly noEmoji: "Emojis are not allowed.";
@@ -1031,6 +1041,7 @@ declare const _default: {
1031
1041
  readonly create: (context: import("../core/types.js").RuleContext<{
1032
1042
  RuleOptions: [{
1033
1043
  allow: string[];
1044
+ style: "emoji" | "gemoji";
1034
1045
  }];
1035
1046
  MessageIds: "noEmoji";
1036
1047
  }>) => {
@@ -1245,6 +1256,44 @@ declare const _default: {
1245
1256
  'root:exit'(): void;
1246
1257
  };
1247
1258
  };
1259
+ 'no-multiple-atx-heading-space': {
1260
+ readonly meta: {
1261
+ readonly type: "layout";
1262
+ readonly docs: {
1263
+ readonly description: "Disallow multiple spaces around ATX heading markers";
1264
+ readonly url: string;
1265
+ readonly recommended: false;
1266
+ readonly stylistic: true;
1267
+ };
1268
+ readonly fixable: "whitespace";
1269
+ readonly schema: [{
1270
+ readonly type: "object";
1271
+ readonly properties: {
1272
+ readonly checkClosedHeading: {
1273
+ readonly type: "boolean";
1274
+ };
1275
+ };
1276
+ readonly additionalProperties: false;
1277
+ }];
1278
+ readonly defaultOptions: [{
1279
+ readonly checkClosedHeading: true;
1280
+ }];
1281
+ readonly messages: {
1282
+ readonly noMultipleAtxHeadingSpace: "Multiple spaces after opening ATX heading markers are not allowed.";
1283
+ readonly noMultipleAtxClosedHeadingSpace: "Multiple spaces before closing ATX heading markers are not allowed.";
1284
+ };
1285
+ readonly language: "markdown";
1286
+ readonly dialects: ["commonmark", "gfm"];
1287
+ };
1288
+ readonly create: (context: import("../core/types.js").RuleContext<{
1289
+ RuleOptions: [{
1290
+ checkClosedHeading: boolean;
1291
+ }];
1292
+ MessageIds: "noMultipleAtxHeadingSpace" | "noMultipleAtxClosedHeadingSpace";
1293
+ }>) => {
1294
+ heading(node: import("mdast").Heading): void;
1295
+ };
1296
+ };
1248
1297
  'no-shell-dollar': {
1249
1298
  readonly meta: {
1250
1299
  readonly type: "problem";
@@ -1313,6 +1362,12 @@ declare const _default: {
1313
1362
  readonly skipInlineCode: {
1314
1363
  readonly type: "boolean";
1315
1364
  };
1365
+ readonly skipMath: {
1366
+ readonly type: "boolean";
1367
+ };
1368
+ readonly skipInlineMath: {
1369
+ readonly type: "boolean";
1370
+ };
1316
1371
  readonly tabWidth: {
1317
1372
  readonly type: "integer";
1318
1373
  readonly minimum: 1;
@@ -1323,6 +1378,8 @@ declare const _default: {
1323
1378
  readonly defaultOptions: [{
1324
1379
  readonly skipCode: true;
1325
1380
  readonly skipInlineCode: true;
1381
+ readonly skipMath: true;
1382
+ readonly skipInlineMath: true;
1326
1383
  readonly tabWidth: 4;
1327
1384
  }];
1328
1385
  readonly messages: {
@@ -1335,12 +1392,16 @@ declare const _default: {
1335
1392
  RuleOptions: [{
1336
1393
  skipCode: boolean | string[];
1337
1394
  skipInlineCode: boolean;
1395
+ skipMath: boolean;
1396
+ skipInlineMath: boolean;
1338
1397
  tabWidth: number;
1339
1398
  }];
1340
1399
  MessageIds: "noTab";
1341
1400
  }>) => {
1342
1401
  code(node: import("mdast").Code): void;
1343
1402
  inlineCode(node: import("mdast").InlineCode): void;
1403
+ math(node: import("mdast-util-math").Math): void;
1404
+ inlineMath(node: import("mdast-util-math").InlineMath): void;
1344
1405
  'root:exit'(): void;
1345
1406
  };
1346
1407
  };
@@ -22,6 +22,7 @@ import noEmoji from './no-emoji.js';
22
22
  import noGitConflictMarker from './no-git-conflict-marker.js';
23
23
  import noIrregularDash from './no-irregular-dash.js';
24
24
  import noIrregularWhitespace from './no-irregular-whitespace.js';
25
+ import noMultipleAtxHeadingSpace from './no-multiple-atx-heading-space.js';
25
26
  import noShellDollar from './no-shell-dollar.js';
26
27
  import noTab from './no-tab.js';
27
28
  import noTrailingHeadingPunctuation from './no-trailing-heading-punctuation.js';
@@ -52,6 +53,7 @@ export default {
52
53
  'no-git-conflict-marker': noGitConflictMarker,
53
54
  'no-irregular-dash': noIrregularDash,
54
55
  'no-irregular-whitespace': noIrregularWhitespace,
56
+ 'no-multiple-atx-heading-space': noMultipleAtxHeadingSpace,
55
57
  'no-shell-dollar': noShellDollar,
56
58
  'no-tab': noTab,
57
59
  'no-trailing-heading-punctuation': noTrailingHeadingPunctuation,
@@ -20,6 +20,11 @@ type RuleOptions = [
20
20
  * @default true
21
21
  */
22
22
  skipCode: boolean | string[];
23
+ /**
24
+ * `true` allows consecutive blank lines in all math blocks.
25
+ * @default true
26
+ */
27
+ skipMath: boolean;
23
28
  }
24
29
  ];
25
30
  declare const _default: {
@@ -50,12 +55,16 @@ declare const _default: {
50
55
  readonly uniqueItems: true;
51
56
  }];
52
57
  };
58
+ readonly skipMath: {
59
+ readonly type: "boolean";
60
+ };
53
61
  };
54
62
  readonly additionalProperties: false;
55
63
  }];
56
64
  readonly defaultOptions: [{
57
65
  readonly max: 1;
58
66
  readonly skipCode: true;
67
+ readonly skipMath: true;
59
68
  }];
60
69
  readonly messages: {
61
70
  readonly noConsecutiveBlankLine: "More than {{ max }} consecutive blank line(s) are not allowed.";
@@ -68,6 +77,7 @@ declare const _default: {
68
77
  MessageIds: "noConsecutiveBlankLine";
69
78
  }>) => {
70
79
  code(node: import("mdast").Code): void;
80
+ math(node: import("mdast-util-math").Math): void;
71
81
  'root:exit'(): void;
72
82
  };
73
83
  };
@@ -43,6 +43,9 @@ export default {
43
43
  },
44
44
  ],
45
45
  },
46
+ skipMath: {
47
+ type: 'boolean',
48
+ },
46
49
  },
47
50
  additionalProperties: false,
48
51
  },
@@ -51,6 +54,7 @@ export default {
51
54
  {
52
55
  max: 1,
53
56
  skipCode: true,
57
+ skipMath: true,
54
58
  },
55
59
  ],
56
60
  messages: {
@@ -61,7 +65,7 @@ export default {
61
65
  },
62
66
  create(context) {
63
67
  const { options, sourceCode } = context;
64
- const [{ max, skipCode }] = options;
68
+ const [{ max, skipCode, skipMath }] = options;
65
69
  const { lines, text } = sourceCode;
66
70
  const skipRanges = new SkipRanges();
67
71
  return {
@@ -69,6 +73,10 @@ export default {
69
73
  if (Array.isArray(skipCode) ? node.lang && skipCode.includes(node.lang) : skipCode)
70
74
  skipRanges.push(sourceCode.getRange(node)); // Store range information of `Code`.
71
75
  },
76
+ math(node) {
77
+ if (skipMath)
78
+ skipRanges.push(sourceCode.getRange(node)); // Store range information of `Math`.
79
+ },
72
80
  'root:exit'() {
73
81
  let consecutiveBlankLineCount = 0;
74
82
  for (let currentLineIdx = 0; currentLineIdx < lines.length; currentLineIdx++) {
@@ -10,10 +10,19 @@ type RuleOptions = [
10
10
  /**
11
11
  * When specified, specific emoji sequences are allowed if they match one of the strings in this array.
12
12
  *
13
- * This is useful when a document intentionally uses a small set of raw Unicode emojis while still disallowing all others.
13
+ * This is useful when a document intentionally uses a small set of raw Unicode emojis or shortcode style emojis
14
+ * while still disallowing all others. When `style` is `'gemoji'`, list shortcodes instead (e.g. `':smiley:'`).
14
15
  * @default []
15
16
  */
16
17
  allow: string[];
18
+ /**
19
+ * Specifies the style of emojis to disallow.
20
+ *
21
+ * - `'emoji'`: Raw Unicode emojis (`😃`).
22
+ * - `'gemoji'`: Shortcode style emojis (`:smiley:`).
23
+ * @default 'emoji'
24
+ */
25
+ style: 'emoji' | 'gemoji';
17
26
  }
18
27
  ];
19
28
  declare const _default: {
@@ -35,11 +44,15 @@ declare const _default: {
35
44
  };
36
45
  readonly uniqueItems: true;
37
46
  };
47
+ readonly style: {
48
+ readonly enum: ["emoji", "gemoji"];
49
+ };
38
50
  };
39
51
  readonly additionalProperties: false;
40
52
  }];
41
53
  readonly defaultOptions: [{
42
54
  readonly allow: [];
55
+ readonly style: "emoji";
43
56
  }];
44
57
  readonly messages: {
45
58
  readonly noEmoji: "Emojis are not allowed.";
@@ -5,11 +5,12 @@
5
5
  // --------------------------------------------------------------------------------
6
6
  // Import
7
7
  // --------------------------------------------------------------------------------
8
- import { URL_RULE_DOCS } from '../core/constants.js';
8
+ import { URL_RULE_DOCS, gemojiRegex as originalGemojiRegex } from '../core/constants.js';
9
9
  // --------------------------------------------------------------------------------
10
10
  // Helper
11
11
  // --------------------------------------------------------------------------------
12
12
  const emojiRegex = /\p{RGI_Emoji}/gv;
13
+ const gemojiRegex = new RegExp(originalGemojiRegex.source, 'g');
13
14
  // --------------------------------------------------------------------------------
14
15
  // Rule Definition
15
16
  // --------------------------------------------------------------------------------
@@ -33,6 +34,9 @@ export default {
33
34
  },
34
35
  uniqueItems: true,
35
36
  },
37
+ style: {
38
+ enum: ['emoji', 'gemoji'],
39
+ },
36
40
  },
37
41
  additionalProperties: false,
38
42
  },
@@ -40,6 +44,7 @@ export default {
40
44
  defaultOptions: [
41
45
  {
42
46
  allow: [],
47
+ style: 'emoji',
43
48
  },
44
49
  ],
45
50
  messages: {
@@ -50,11 +55,13 @@ export default {
50
55
  },
51
56
  create(context) {
52
57
  const { sourceCode } = context;
53
- const [{ allow }] = context.options;
58
+ const [{ allow, style }] = context.options;
54
59
  return {
55
60
  text(node) {
56
61
  const [nodeStartOffset] = sourceCode.getRange(node);
57
- const matches = sourceCode.getText(node).matchAll(emojiRegex);
62
+ const matches = sourceCode
63
+ .getText(node)
64
+ .matchAll(style === 'emoji' ? emojiRegex : gemojiRegex);
58
65
  for (const match of matches) {
59
66
  const emoji = match[0];
60
67
  if (allow.includes(emoji))
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Rule to disallow multiple spaces around ATX heading markers.
3
+ * @author Ga eun Lee(tooth-is-silver)
4
+ */
5
+ /**
6
+ * Options for the `no-multiple-atx-heading-space` rule.
7
+ */
8
+ type RuleOptions = [
9
+ {
10
+ /**
11
+ * When `checkClosedHeading` is set to `false`, this rule stops checking for multiple
12
+ * consecutive spaces or tabs before the closing hash characters in closed ATX headings.
13
+ * @default true
14
+ */
15
+ checkClosedHeading: boolean;
16
+ }
17
+ ];
18
+ type MessageIds = 'noMultipleAtxHeadingSpace' | 'noMultipleAtxClosedHeadingSpace';
19
+ declare const _default: {
20
+ readonly meta: {
21
+ readonly type: "layout";
22
+ readonly docs: {
23
+ readonly description: "Disallow multiple spaces around ATX heading markers";
24
+ readonly url: string;
25
+ readonly recommended: false;
26
+ readonly stylistic: true;
27
+ };
28
+ readonly fixable: "whitespace";
29
+ readonly schema: [{
30
+ readonly type: "object";
31
+ readonly properties: {
32
+ readonly checkClosedHeading: {
33
+ readonly type: "boolean";
34
+ };
35
+ };
36
+ readonly additionalProperties: false;
37
+ }];
38
+ readonly defaultOptions: [{
39
+ readonly checkClosedHeading: true;
40
+ }];
41
+ readonly messages: {
42
+ readonly noMultipleAtxHeadingSpace: "Multiple spaces after opening ATX heading markers are not allowed.";
43
+ readonly noMultipleAtxClosedHeadingSpace: "Multiple spaces before closing ATX heading markers are not allowed.";
44
+ };
45
+ readonly language: "markdown";
46
+ readonly dialects: ["commonmark", "gfm"];
47
+ };
48
+ readonly create: (context: import("../core/types.js").RuleContext<{
49
+ RuleOptions: RuleOptions;
50
+ MessageIds: MessageIds;
51
+ }>) => {
52
+ heading(node: import("mdast").Heading): void;
53
+ };
54
+ };
55
+ export default _default;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @fileoverview Rule to disallow multiple spaces around ATX heading markers.
3
+ * @author Ga eun Lee(tooth-is-silver)
4
+ */
5
+ // --------------------------------------------------------------------------------
6
+ // Import
7
+ // --------------------------------------------------------------------------------
8
+ import { getHeadingStyle } from '../core/utils/index.js';
9
+ import { URL_RULE_DOCS } from '../core/constants.js';
10
+ // --------------------------------------------------------------------------------
11
+ // Helper
12
+ // --------------------------------------------------------------------------------
13
+ const multipleSpacesRegex = /^[ \t]{2,}/u;
14
+ // --------------------------------------------------------------------------------
15
+ // Rule Definition
16
+ // --------------------------------------------------------------------------------
17
+ export default {
18
+ meta: {
19
+ type: 'layout',
20
+ docs: {
21
+ description: 'Disallow multiple spaces around ATX heading markers',
22
+ url: URL_RULE_DOCS('no-multiple-atx-heading-space'),
23
+ recommended: false,
24
+ stylistic: true,
25
+ },
26
+ fixable: 'whitespace',
27
+ schema: [
28
+ {
29
+ type: 'object',
30
+ properties: {
31
+ checkClosedHeading: {
32
+ type: 'boolean',
33
+ },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ ],
38
+ defaultOptions: [
39
+ {
40
+ checkClosedHeading: true,
41
+ },
42
+ ],
43
+ messages: {
44
+ noMultipleAtxHeadingSpace: 'Multiple spaces after opening ATX heading markers are not allowed.',
45
+ noMultipleAtxClosedHeadingSpace: 'Multiple spaces before closing ATX heading markers are not allowed.',
46
+ },
47
+ language: 'markdown',
48
+ dialects: ['commonmark', 'gfm'],
49
+ },
50
+ create(context) {
51
+ const { sourceCode } = context;
52
+ const [{ checkClosedHeading }] = context.options;
53
+ /**
54
+ * @param text Text starting immediately after an opening sequence or heading content.
55
+ * @param textStartOffset Start offset of the text.
56
+ * @param messageId Message for the opening or closing sequence.
57
+ */
58
+ function report(text, textStartOffset, messageId) {
59
+ const spacesMatch = multipleSpacesRegex.exec(text)?.[0];
60
+ if (!spacesMatch)
61
+ return;
62
+ // Empty ATX headings keep no whitespace; otherwise, keep the first character.
63
+ const startOffset = textStartOffset + Number(spacesMatch.length !== text.length);
64
+ const endOffset = textStartOffset + spacesMatch.length;
65
+ context.report({
66
+ loc: {
67
+ start: sourceCode.getLocFromIndex(startOffset),
68
+ end: sourceCode.getLocFromIndex(endOffset),
69
+ },
70
+ messageId,
71
+ fix(fixer) {
72
+ return fixer.removeRange([startOffset, endOffset]);
73
+ },
74
+ });
75
+ }
76
+ return {
77
+ heading(node) {
78
+ const currentHeadingStyle = getHeadingStyle(node, sourceCode);
79
+ if (currentHeadingStyle === 'setext') {
80
+ // Early returns when the heading is a Setext heading, as this rule only applies to ATX headings.
81
+ return;
82
+ }
83
+ const lastChildNode = node.children.at(-1);
84
+ const isClosedHeading = currentHeadingStyle === 'atx-closed';
85
+ const isEmptyHeading = !lastChildNode;
86
+ if (!checkClosedHeading && isClosedHeading && isEmptyHeading) {
87
+ // When closing checks are disabled, skip closed headings with no content
88
+ // (e.g., `'## ##'`), where opening and closing whitespace overlap.
89
+ return;
90
+ }
91
+ const text = sourceCode.getText(node);
92
+ const [nodeStartOffset] = sourceCode.getRange(node);
93
+ report(text.slice(node.depth), nodeStartOffset + node.depth, 'noMultipleAtxHeadingSpace');
94
+ if (checkClosedHeading && isClosedHeading && !isEmptyHeading) {
95
+ const [, lastChildNodeEndOffset] = sourceCode.getRange(lastChildNode);
96
+ report(text.slice(lastChildNodeEndOffset - nodeStartOffset), lastChildNodeEndOffset, 'noMultipleAtxClosedHeadingSpace');
97
+ }
98
+ },
99
+ };
100
+ },
101
+ };
@@ -17,6 +17,16 @@ type RuleOptions = [
17
17
  * @default true
18
18
  */
19
19
  skipInlineCode: boolean;
20
+ /**
21
+ * `true` allows tabs in all math blocks.
22
+ * @default true
23
+ */
24
+ skipMath: boolean;
25
+ /**
26
+ * `true` allows tabs in all inline math.
27
+ * @default true
28
+ */
29
+ skipInlineMath: boolean;
20
30
  /**
21
31
  * Number of spaces to replace each tab with when applying an autofix.
22
32
  * @default 4
@@ -51,6 +61,12 @@ declare const _default: {
51
61
  readonly skipInlineCode: {
52
62
  readonly type: "boolean";
53
63
  };
64
+ readonly skipMath: {
65
+ readonly type: "boolean";
66
+ };
67
+ readonly skipInlineMath: {
68
+ readonly type: "boolean";
69
+ };
54
70
  readonly tabWidth: {
55
71
  readonly type: "integer";
56
72
  readonly minimum: 1;
@@ -61,6 +77,8 @@ declare const _default: {
61
77
  readonly defaultOptions: [{
62
78
  readonly skipCode: true;
63
79
  readonly skipInlineCode: true;
80
+ readonly skipMath: true;
81
+ readonly skipInlineMath: true;
64
82
  readonly tabWidth: 4;
65
83
  }];
66
84
  readonly messages: {
@@ -75,6 +93,8 @@ declare const _default: {
75
93
  }>) => {
76
94
  code(node: import("mdast").Code): void;
77
95
  inlineCode(node: import("mdast").InlineCode): void;
96
+ math(node: import("mdast-util-math").Math): void;
97
+ inlineMath(node: import("mdast-util-math").InlineMath): void;
78
98
  'root:exit'(): void;
79
99
  };
80
100
  };
@@ -45,6 +45,12 @@ export default {
45
45
  skipInlineCode: {
46
46
  type: 'boolean',
47
47
  },
48
+ skipMath: {
49
+ type: 'boolean',
50
+ },
51
+ skipInlineMath: {
52
+ type: 'boolean',
53
+ },
48
54
  tabWidth: {
49
55
  type: 'integer',
50
56
  minimum: 1,
@@ -57,6 +63,8 @@ export default {
57
63
  {
58
64
  skipCode: true,
59
65
  skipInlineCode: true,
66
+ skipMath: true,
67
+ skipInlineMath: true,
60
68
  tabWidth: 4,
61
69
  },
62
70
  ],
@@ -68,7 +76,7 @@ export default {
68
76
  },
69
77
  create(context) {
70
78
  const { sourceCode } = context;
71
- const [{ skipCode, skipInlineCode, tabWidth }] = context.options;
79
+ const [{ skipCode, skipInlineCode, skipMath, skipInlineMath, tabWidth }] = context.options;
72
80
  const skipRanges = new SkipRanges();
73
81
  return {
74
82
  code(node) {
@@ -79,6 +87,14 @@ export default {
79
87
  if (skipInlineCode)
80
88
  skipRanges.push(sourceCode.getRange(node)); // Store range information of `InlineCode`.
81
89
  },
90
+ math(node) {
91
+ if (skipMath)
92
+ skipRanges.push(sourceCode.getRange(node)); // Store range information of `Math`.
93
+ },
94
+ inlineMath(node) {
95
+ if (skipInlineMath)
96
+ skipRanges.push(sourceCode.getRange(node)); // Store range information of `InlineMath`.
97
+ },
82
98
  'root:exit'() {
83
99
  const matches = sourceCode.text.matchAll(tabRegex);
84
100
  for (const match of matches) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-markdown",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Lint your Markdown with ESLint. Additional rules for use with `@eslint/markdown`.🛠️",