eslint-plugin-md-style 0.3.0 → 0.3.1

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/dist/index.d.mts CHANGED
@@ -1,5 +1,48 @@
1
+ import "@eslint/markdown";
1
2
  import { ESLint, Linter } from "eslint";
2
- //#region src/index.d.ts
3
+ //#region types/typegen.d.ts
4
+ interface RuleOptions {
5
+ /**
6
+ * Enforce padding around VitePress custom containers.
7
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/padding-around-custom-container.md
8
+ */
9
+ 'md-style/padding-around-custom-container'?: Linter.RuleEntry<MdStylePaddingAroundCustomContainer>;
10
+ /**
11
+ * Enforce spacing around VitePress custom-container markers.
12
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/space-around-custom-container.md
13
+ */
14
+ 'md-style/space-around-custom-container'?: Linter.RuleEntry<[]>;
15
+ /**
16
+ * Enforce spacing around Markdown inline elements.
17
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/space-around-inline-element.md
18
+ */
19
+ 'md-style/space-around-inline-element'?: Linter.RuleEntry<[]>;
20
+ /**
21
+ * Enforce a single space between CJK characters and numbers.
22
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/space-around-number.md
23
+ */
24
+ 'md-style/space-around-number'?: Linter.RuleEntry<[]>;
25
+ /**
26
+ * Enforce a single space between CJK characters and Latin words.
27
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/space-around-word.md
28
+ */
29
+ 'md-style/space-around-word'?: Linter.RuleEntry<[]>;
30
+ /**
31
+ * Require custom containers to use a supported type.
32
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/valid-custom-container-type.md
33
+ */
34
+ 'md-style/valid-custom-container-type'?: Linter.RuleEntry<[]>;
35
+ /**
36
+ * Require strict lowercase anchors for headings that contain CJK text.
37
+ * @see https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/valid-heading-anchor.md
38
+ */
39
+ 'md-style/valid-heading-anchor'?: Linter.RuleEntry<[]>;
40
+ }
41
+ /* ======= Declarations ======= */
42
+ // ----- md-style/padding-around-custom-container -----
43
+ type MdStylePaddingAroundCustomContainer = [] | [("compact" | "loose")];
44
+ //#endregion
45
+ //#region index.d.ts
3
46
  declare const plugin: ESLint.Plugin;
4
47
  interface PluginConfigMap {
5
48
  recommended: Linter.Config;
@@ -11,4 +54,4 @@ type MdStylePlugin = ESLint.Plugin & {
11
54
  };
12
55
  declare const mdStylePlugin: MdStylePlugin;
13
56
  //#endregion
14
- export { MdStylePlugin, configs, mdStylePlugin as default, plugin };
57
+ export { MdStylePlugin, type RuleOptions, configs, mdStylePlugin as default, plugin };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import markdown, { MarkdownLanguage } from "@eslint/markdown";
2
- //#region src/parser/ast.ts
2
+ //#region parser/ast.ts
3
3
  function isObject(val) {
4
4
  return !!val && typeof val === "object";
5
5
  }
@@ -87,7 +87,7 @@ function getAdjacentChar(str, position) {
87
87
  return position === "head" ? str[0] : str[str.length - 1];
88
88
  }
89
89
  //#endregion
90
- //#region src/parser/custom-container/parse.ts
90
+ //#region parser/custom-container/parser.ts
91
91
  /**
92
92
  * Parses top-level text into container, blank-line, and text nodes.
93
93
  */
@@ -344,18 +344,22 @@ function getCodeNodeRanges(node) {
344
344
  return ranges;
345
345
  }
346
346
  //#endregion
347
- //#region src/utils/index.ts
348
- function createRule({ create, defaultOptions, meta }) {
347
+ //#region utils/index.ts
348
+ function createRule({ name, create, defaultOptions, meta }) {
349
349
  return {
350
350
  create,
351
351
  meta: {
352
352
  defaultOptions,
353
- ...meta
353
+ ...meta,
354
+ docs: {
355
+ ...meta?.docs,
356
+ url: `https://github.com/NoiseFan/eslint-plugin-md-style/blob/main/docs/rules/en/${name}.md`
357
+ }
354
358
  }
355
359
  };
356
360
  }
357
361
  //#endregion
358
- //#region src/rules/padding-around-custom-container/analyzs.ts
362
+ //#region rules/padding-around-custom-container/analyzs.ts
359
363
  /**
360
364
  * Analyzes custom containers and returns de-duplicated padding issues.
361
365
  */
@@ -402,7 +406,7 @@ function analyzeInnerBoundary(children, containerBoundary, opts) {
402
406
  });
403
407
  }
404
408
  /**
405
- * Checks one side of a container's inner boundary for the selected mode.
409
+ * Checks one side of a container's inner boundary for the selected mode.
406
410
  */
407
411
  function checkInnerSide(node, opts) {
408
412
  const { direction, children, offset } = opts;
@@ -440,7 +444,7 @@ function getContainerBoundary(container) {
440
444
  };
441
445
  }
442
446
  /**
443
- * Checks both sides of a container for the required external blank line.
447
+ * Checks both sides of a container for the required external blank line.
444
448
  */
445
449
  function analyzeOuterBoundary(children, boundaryIndex, opts) {
446
450
  checkOuterSide(children, {
@@ -523,7 +527,7 @@ function getLineBreakFromChildren(children) {
523
527
  return isBlankNode(blank) ? getLineBreak(blank.value) : "\n";
524
528
  }
525
529
  //#endregion
526
- //#region src/rules/padding-around-custom-container/index.ts
530
+ //#region rules/padding-around-custom-container/index.ts
527
531
  const RULE_NAME$6 = "padding-around-custom-container";
528
532
  const MESSAGE_IDS$4 = {
529
533
  missing: "missing",
@@ -533,7 +537,10 @@ var padding_around_custom_container_default = createRule({
533
537
  name: RULE_NAME$6,
534
538
  meta: {
535
539
  type: "layout",
536
- docs: { description: "Enforce padding around VitePress custom containers." },
540
+ docs: {
541
+ description: "Enforce padding around VitePress custom containers.",
542
+ recommended: true
543
+ },
537
544
  messages: {
538
545
  missing: "A custom container must be separated from surrounding content by one blank line.",
539
546
  unexpected: "Unexpected blank lines around a custom container."
@@ -569,7 +576,7 @@ var padding_around_custom_container_default = createRule({
569
576
  }
570
577
  });
571
578
  //#endregion
572
- //#region src/rules/space-around-custom-container/analyzs.ts
579
+ //#region rules/space-around-custom-container/analyzs.ts
573
580
  /**
574
581
  * Finds custom-container marker lines and normalizes their surrounding spaces.
575
582
  *
@@ -621,7 +628,7 @@ function getMarkerMessageId(tag) {
621
628
  return MESSAGE_IDS$3.unexpectedSeparator;
622
629
  }
623
630
  //#endregion
624
- //#region src/rules/space-around-custom-container/index.ts
631
+ //#region rules/space-around-custom-container/index.ts
625
632
  const RULE_NAME$5 = "space-around-custom-container";
626
633
  const MESSAGE_IDS$3 = {
627
634
  unexpectedIndentation: "unexpectedIndentation",
@@ -664,7 +671,7 @@ var space_around_custom_container_default = createRule({
664
671
  }
665
672
  });
666
673
  //#endregion
667
- //#region src/utils/anchor.ts
674
+ //#region utils/anchor.ts
668
675
  /**
669
676
  * Match the trailing anchor-like fragment from a heading or adjacent text.
670
677
  * @example `中文标题 {#Chinese-Title}` -> `{#Chinese-Title}`
@@ -693,7 +700,7 @@ function getLikeAnchor(str) {
693
700
  };
694
701
  }
695
702
  //#endregion
696
- //#region src/utils/punctuation.ts
703
+ //#region utils/punctuation.ts
697
704
  const OPENING_PAIRED_PUNCTUATION = /* @__PURE__ */ new Set([
698
705
  "(",
699
706
  "[",
@@ -774,7 +781,7 @@ function hasPunctuation(str, position = "head") {
774
781
  else return isPunctuation(str[str.length - 1]);
775
782
  }
776
783
  //#endregion
777
- //#region src/utils/space.ts
784
+ //#region utils/space.ts
778
785
  /**
779
786
  * Gets the count and range of consecutive whitespace at the start or end of a string.
780
787
  * @example ` text`, `head` -> { count: 2, start: 0, end: 2 }
@@ -806,7 +813,7 @@ function getWhiteSpace(str, position = "head") {
806
813
  }
807
814
  }
808
815
  //#endregion
809
- //#region src/rules/space-around-inline-element/analyze.ts
816
+ //#region rules/space-around-inline-element/analyze.ts
810
817
  /**
811
818
  * Validates whether a spacing run contains exactly one required space.
812
819
  */
@@ -863,13 +870,6 @@ const INLINE_ELEMENT_TYPES = /* @__PURE__ */ new Set([
863
870
  function isInlineElement(node) {
864
871
  return !!node && INLINE_ELEMENT_TYPES.has(node.type);
865
872
  }
866
- /**
867
- * Checks whether the current inline element is nested inside another selected inline element.
868
- */
869
- function isNestedInlineElement(nodeContext) {
870
- const { parent } = nodeContext;
871
- return isInlineElement(parent);
872
- }
873
873
  function getSpaceContext(nodeContext) {
874
874
  const { prev, next } = nodeContext;
875
875
  const prevValue = getNodeValue(prev);
@@ -927,7 +927,7 @@ function validateSpace(nodeContext) {
927
927
  return validateDefaultSpace(nodeContext);
928
928
  }
929
929
  //#endregion
930
- //#region src/rules/space-around-inline-element/index.ts
930
+ //#region rules/space-around-inline-element/index.ts
931
931
  const RULE_NAME$4 = "space-around-inline-element";
932
932
  const MESSAGE_IDS$2 = {
933
933
  missingSpaceBefore: "missingSpaceBefore",
@@ -957,7 +957,10 @@ var space_around_inline_element_default = createRule({
957
957
  name: RULE_NAME$4,
958
958
  meta: {
959
959
  type: "layout",
960
- docs: { description: "Enforce spacing around Markdown inline elements." },
960
+ docs: {
961
+ description: "Enforce spacing around Markdown inline elements.",
962
+ recommended: true
963
+ },
961
964
  messages: {
962
965
  missingSpaceBefore: "A space is required before the inline element.",
963
966
  missingSpaceAfter: "A space is required after the inline element.",
@@ -999,7 +1002,6 @@ function checkInlineElement(context, node) {
999
1002
  /* v8 ignore if -- @preserve */
1000
1003
  if (!position) return;
1001
1004
  const nodeContext = getNodeContext(context, node);
1002
- if (isNestedInlineElement(nodeContext)) return;
1003
1005
  const spaceContext = getSpaceContext(nodeContext);
1004
1006
  const issue = validateSpace(nodeContext);
1005
1007
  if (!issue) return;
@@ -1029,7 +1031,7 @@ function checkInlineElement(context, node) {
1029
1031
  }
1030
1032
  }
1031
1033
  //#endregion
1032
- //#region src/utils/text/tokenizer.ts
1034
+ //#region parser/text/parser.ts
1033
1035
  const TEXT_TYPE = {
1034
1036
  "cjk": "cjk",
1035
1037
  "latin": "latin",
@@ -1206,7 +1208,7 @@ function buildTextNodeAst(node) {
1206
1208
  };
1207
1209
  }
1208
1210
  //#endregion
1209
- //#region src/rules/shared/text-boundary-spacing.ts
1211
+ //#region rules/shared/text-boundary-spacing.ts
1210
1212
  /**
1211
1213
  * Normalizes an existing space token around the target token type.
1212
1214
  * When multiple spaces are collapsed, the redundant side is recorded so the
@@ -1351,7 +1353,7 @@ var space_around_word_default = createRule({
1351
1353
  }
1352
1354
  });
1353
1355
  //#endregion
1354
- //#region src/rules/valid-custom-container-type/index.ts
1356
+ //#region rules/valid-custom-container-type/index.ts
1355
1357
  const RULE_NAME$1 = "valid-custom-container-type";
1356
1358
  const MESSAGE_IDS$1 = {
1357
1359
  invalidType: "invalidType",
@@ -1361,7 +1363,10 @@ var valid_custom_container_type_default = createRule({
1361
1363
  name: RULE_NAME$1,
1362
1364
  meta: {
1363
1365
  type: "problem",
1364
- docs: { description: "Require custom containers to use a supported type." },
1366
+ docs: {
1367
+ description: "Require custom containers to use a supported type.",
1368
+ recommended: true
1369
+ },
1365
1370
  messages: {
1366
1371
  invalidType: "Invalid custom container type \"{{type}}\". Use info, tip, warning, danger, details, raw, code-group, v-pre, or tabs.",
1367
1372
  invalidTypeCase: "Custom container type \"{{type}}\" must be lowercase."
@@ -1415,7 +1420,7 @@ function getTypeIssue(type) {
1415
1420
  return { messageId: MESSAGE_IDS$1.invalidType };
1416
1421
  }
1417
1422
  //#endregion
1418
- //#region src/rules/valid-heading-anchor/anchor.ts
1423
+ //#region rules/valid-heading-anchor/anchor.ts
1419
1424
  /**
1420
1425
  * Check if the string has an anchor.
1421
1426
  * @example: {#chinese-anchor}
@@ -1453,7 +1458,7 @@ function calcAnchorPositionCompensate(content) {
1453
1458
  return match.length - anchor.rawLikeAnchor.length;
1454
1459
  }
1455
1460
  //#endregion
1456
- //#region src/parser/markdown.ts
1461
+ //#region parser/markdown.ts
1457
1462
  const language = new MarkdownLanguage({ mode: "gfm" });
1458
1463
  /**
1459
1464
  * Parses Markdown with the same GFM language implementation used by the
@@ -1478,7 +1483,7 @@ function parseMarkdown(markdown) {
1478
1483
  };
1479
1484
  }
1480
1485
  //#endregion
1481
- //#region src/rules/valid-heading-anchor/frontmatter.ts
1486
+ //#region rules/valid-heading-anchor/frontmatter.ts
1482
1487
  /**
1483
1488
  * Returns true when the Markdown document starts with YAML frontmatter.
1484
1489
  */
@@ -1488,14 +1493,14 @@ function hasFrontmatter(markdown, prevNode) {
1488
1493
  return ast.children[0]?.type === "yaml";
1489
1494
  }
1490
1495
  //#endregion
1491
- //#region src/rules/valid-heading-anchor/index.ts
1496
+ //#region rules/valid-heading-anchor/index.ts
1492
1497
  const RULE_NAME = "valid-heading-anchor";
1493
1498
  const MESSAGE_IDS = {
1494
1499
  missingAnchor: "missingAnchor",
1495
1500
  invalidHeadingAnchor: "invalidHeadingAnchor"
1496
1501
  };
1497
1502
  //#endregion
1498
- //#region src/rules/index.ts
1503
+ //#region rules/index.ts
1499
1504
  const rules = {
1500
1505
  "padding-around-custom-container": padding_around_custom_container_default,
1501
1506
  "space-around-inline-element": space_around_inline_element_default,
@@ -1507,7 +1512,10 @@ const rules = {
1507
1512
  name: RULE_NAME,
1508
1513
  meta: {
1509
1514
  type: "layout",
1510
- docs: { description: "Require strict lowercase anchors for headings that contain CJK text." },
1515
+ docs: {
1516
+ description: "Require strict lowercase anchors for headings that contain CJK text.",
1517
+ recommended: true
1518
+ },
1511
1519
  messages: {
1512
1520
  missingAnchor: "Non-ASCII heading must have an anchor in the format \"{#lowercase-anchor}\".",
1513
1521
  invalidHeadingAnchor: "Anchor must use lowercase letters and valid characters only."
@@ -1534,14 +1542,13 @@ const rules = {
1534
1542
  }
1535
1543
  const { rawLikeAnchor, isLikeAnchor } = liked;
1536
1544
  const compensate = calcAnchorPositionCompensate(source);
1537
- const remainingContent = source.slice(0, -rawLikeAnchor.length - compensate).trim();
1538
- const anchor = normalizeAnchor(rawLikeAnchor);
1539
- if (rawLikeAnchor === anchor) return;
1545
+ const fixed = `${source.slice(0, -rawLikeAnchor.length - compensate).trim()} {#${normalizeAnchor(rawLikeAnchor)}}`;
1546
+ if (source === fixed) return;
1540
1547
  context.report({
1541
1548
  node,
1542
1549
  messageId: isLikeAnchor ? MESSAGE_IDS.missingAnchor : MESSAGE_IDS.invalidHeadingAnchor,
1543
1550
  fix(fixer) {
1544
- return fixer.replaceTextRange([start, end], `${remainingContent} {#${anchor}}`);
1551
+ return fixer.replaceTextRange([start, end], fixed);
1545
1552
  }
1546
1553
  });
1547
1554
  } };
@@ -1549,18 +1556,13 @@ const rules = {
1549
1556
  })
1550
1557
  };
1551
1558
  //#endregion
1552
- //#region src/index.ts
1559
+ //#region index.ts
1553
1560
  const plugin = {
1554
1561
  rules,
1555
1562
  processors: markdown.processors,
1556
1563
  languages: { gfm: new MarkdownLanguage({ mode: "gfm" }) }
1557
1564
  };
1558
- const recommendedRules = {
1559
- "md-style/valid-heading-anchor": "error",
1560
- "md-style/space-around-inline-element": "error",
1561
- "md-style/padding-around-custom-container": "error",
1562
- "md-style/valid-custom-container-type": "error"
1563
- };
1565
+ const recommendedRules = Object.fromEntries(Object.entries(rules).filter(([, rule]) => rule.meta?.docs?.recommended).map(([ruleName]) => [`md-style/${ruleName}`, "error"]));
1564
1566
  const allRules = Object.fromEntries(Object.keys(rules).map((ruleName) => [`md-style/${ruleName}`, "error"]));
1565
1567
  const configs = {
1566
1568
  recommended: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-md-style",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "packageManager": "pnpm@10.21.0",
6
6
  "description": "ESLint plugin for enforcing style rules in Markdown-based documentation",
7
7
  "author": "noisefan <noisefan@163.com>",
@@ -9,7 +9,8 @@
9
9
  "homepage": "https://github.com/NoiseFan/eslint-plugin-md-style#readme",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+github.com:NoiseFan/eslint-plugin-md-style.git"
12
+ "url": "git+https://github.com/NoiseFan/eslint-plugin-md-style",
13
+ "directory": "packages/eslint-plugin-md-style"
13
14
  },
14
15
  "bugs": {
15
16
  "url": "https://github.com/NoiseFan/eslint-plugin-md-style/issues"
@@ -29,16 +30,15 @@
29
30
  "node": ">=20.19.0"
30
31
  },
31
32
  "scripts": {
32
- "lint": "eslint .",
33
- "lint:fix": "pnpm run lint --fix",
34
- "build": "tsdown",
33
+ "build": "pnpm gen:types && tsdown",
34
+ "gen:types": "tsx scripts/typegen.ts",
35
+ "gen:rules": "tsx scripts/readme.ts",
36
+ "publish:ci": "tsx scripts/publish.ts",
35
37
  "dev": "tsdown --watch",
36
38
  "test": "vitest",
37
39
  "test:cov": "vitest --coverage",
38
40
  "typecheck": "tsc --noEmit",
39
- "release": "bumpp",
40
- "prepublishOnly": "pnpm run build",
41
- "prepare": "simple-git-hooks"
41
+ "prepublishOnly": "pnpm gen:rules && pnpm build"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@antfu/eslint-config": "^7.5.0",
@@ -51,33 +51,17 @@
51
51
  }
52
52
  },
53
53
  "devDependencies": {
54
- "@antfu/eslint-config": "^9.3.0",
55
- "@eslint/markdown": "^8.0.3",
56
- "@types/mdast": "^4.0.4",
57
- "@types/node": "^26.2.0",
58
- "@types/unist": "^3.0.3",
59
- "@typescript-eslint/utils": "^8.67.0",
60
- "@vitest/coverage-v8": "^4.1.11",
61
- "bumpp": "^12.2.1",
62
- "eslint": "10.8.1",
63
- "eslint-factory": "^0.1.2",
64
- "eslint-plugin-format": "^2.0.1",
65
- "eslint-plugin-md-style": "^0.2.0",
66
- "eslint-vitest-rule-tester": "^3.1.0",
67
- "lint-staged": "^17.3.0",
68
- "simple-git-hooks": "^2.13.1",
69
- "tinyglobby": "^0.2.17",
70
- "tsdown": "^0.22.14",
71
- "typescript": "^6.0.3",
72
- "vitest": "^4.1.11",
73
- "yaml": "^2.9.0"
74
- },
75
- "simple-git-hooks": {
76
- "pre-commit": "pnpx lint-staged"
77
- },
78
- "lint-staged": {
79
- "*.{js,ts,md,yml,yaml,json}": [
80
- "eslint --cache --fix"
81
- ]
54
+ "@eslint/markdown": "catalog:lint",
55
+ "@types/mdast": "catalog:types",
56
+ "@types/node": "catalog:types",
57
+ "@types/unist": "catalog:types",
58
+ "@typescript-eslint/utils": "catalog:dev",
59
+ "@vitest/coverage-v8": "catalog:test",
60
+ "eslint-plugin-format": "catalog:lint",
61
+ "eslint-plugin-md-style": "catalog:lint",
62
+ "eslint-typegen": "catalog:lint",
63
+ "eslint-vitest-rule-tester": "catalog:lint",
64
+ "tinyglobby": "catalog:dev",
65
+ "yaml": "catalog:dev"
82
66
  }
83
67
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright © 2025-PRESENT Kevin Deng (https://github.com/sxzz)
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.