eslint-plugin-markdown-preferences 0.36.3 → 0.38.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
@@ -144,15 +144,16 @@ The rules with the following 💄 are included in the `standard` config.
144
144
 
145
145
  <!-- prettier-ignore-start -->
146
146
 
147
- | Rule ID | Description | Fixable | Config |
148
- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------- | :-----: | :----: |
149
- | [markdown-preferences/canonical-code-block-language](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/canonical-code-block-language.html) | enforce canonical language names in code blocks | 🔧 | |
150
- | [markdown-preferences/emoji-notation](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emoji-notation.html) | enforce consistent emoji notation style in Markdown files. | 🔧 | |
151
- | [markdown-preferences/heading-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/heading-casing.html) | enforce consistent casing in headings. | 🔧 | |
152
- | [markdown-preferences/ordered-list-marker-start](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html) | enforce that ordered list markers start with 1 or 0 | 🔧 | 💄 |
153
- | [markdown-preferences/prefer-inline-code-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html) | enforce the use of inline code for specific words. | 🔧 | |
154
- | [markdown-preferences/prefer-linked-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html) | enforce the specified word to be a link. | 🔧 | |
155
- | [markdown-preferences/table-header-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-header-casing.html) | enforce consistent casing in table header cells. | 🔧 | |
147
+ | Rule ID | Description | Fixable | Config |
148
+ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :-----: | :----: |
149
+ | [markdown-preferences/canonical-code-block-language](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/canonical-code-block-language.html) | enforce canonical language names in code blocks | 🔧 | |
150
+ | [markdown-preferences/emoji-notation](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emoji-notation.html) | enforce consistent emoji notation style in Markdown files. | 🔧 | |
151
+ | [markdown-preferences/heading-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/heading-casing.html) | enforce consistent casing in headings. | 🔧 | |
152
+ | [markdown-preferences/no-heading-trailing-punctuation](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-heading-trailing-punctuation.html) | disallow trailing punctuation in headings. | | |
153
+ | [markdown-preferences/ordered-list-marker-start](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html) | enforce that ordered list markers start with 1 or 0 | 🔧 | 💄 |
154
+ | [markdown-preferences/prefer-inline-code-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html) | enforce the use of inline code for specific words. | 🔧 | |
155
+ | [markdown-preferences/prefer-linked-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html) | enforce the specified word to be a link. | 🔧 | |
156
+ | [markdown-preferences/table-header-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-header-casing.html) | enforce consistent casing in table header cells. | 🔧 | |
156
157
 
157
158
  <!-- prettier-ignore-end -->
158
159
 
@@ -203,6 +204,7 @@ The rules with the following 💄 are included in the `standard` config.
203
204
  | [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 | 🔧 | ⭐💄 |
204
205
  | [markdown-preferences/no-multi-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multi-spaces.html) | disallow multiple spaces | 🔧 | 💄 |
205
206
  | [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. | 🔧 | 💄 |
207
+ | [markdown-preferences/no-tabs](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-tabs.html) | disallow tab characters in Markdown files. | 🔧 | 💄 |
206
208
  | [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. | 🔧 | 💄 |
207
209
  | [markdown-preferences/padded-custom-containers](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padded-custom-containers.html) | disallow or require padding inside custom containers | 🔧 | 💄 |
208
210
  | [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 | 🔧 | 💄 |
@@ -0,0 +1,18 @@
1
+ //#region rolldown:runtime
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (all, symbols) => {
4
+ let target = {};
5
+ for (var name in all) {
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ }
11
+ if (symbols) {
12
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ }
14
+ return target;
15
+ };
16
+
17
+ //#endregion
18
+ export { __export as t };
package/lib/index.d.ts CHANGED
@@ -140,6 +140,11 @@ interface RuleOptions {
140
140
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html
141
141
  */
142
142
  'markdown-preferences/list-marker-alignment'?: Linter.RuleEntry<MarkdownPreferencesListMarkerAlignment>;
143
+ /**
144
+ * disallow trailing punctuation in headings.
145
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-heading-trailing-punctuation.html
146
+ */
147
+ 'markdown-preferences/no-heading-trailing-punctuation'?: Linter.RuleEntry<MarkdownPreferencesNoHeadingTrailingPunctuation>;
143
148
  /**
144
149
  * disallow implicit block closing for fenced code blocks, math blocks, and custom containers
145
150
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-implicit-block-closing.html
@@ -160,6 +165,11 @@ interface RuleOptions {
160
165
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multiple-empty-lines.html
161
166
  */
162
167
  'markdown-preferences/no-multiple-empty-lines'?: Linter.RuleEntry<MarkdownPreferencesNoMultipleEmptyLines>;
168
+ /**
169
+ * disallow tab characters in Markdown files.
170
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-tabs.html
171
+ */
172
+ 'markdown-preferences/no-tabs'?: Linter.RuleEntry<MarkdownPreferencesNoTabs>;
163
173
  /**
164
174
  * disallow text backslash at the end of a line.
165
175
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html
@@ -393,11 +403,42 @@ type MarkdownPreferencesLinkTitleStyle = [] | [{
393
403
  type MarkdownPreferencesListMarkerAlignment = [] | [{
394
404
  align?: ("left" | "right");
395
405
  }];
406
+ type MarkdownPreferencesNoHeadingTrailingPunctuation = [] | [{
407
+ punctuation?: (string | {
408
+ "1"?: string;
409
+ "2"?: string;
410
+ "3"?: string;
411
+ "4"?: string;
412
+ "5"?: string;
413
+ "6"?: string;
414
+ default?: string;
415
+ "1-2"?: string;
416
+ "1-3"?: string;
417
+ "1-4"?: string;
418
+ "1-5"?: string;
419
+ "1-6"?: string;
420
+ "2-3"?: string;
421
+ "2-4"?: string;
422
+ "2-5"?: string;
423
+ "2-6"?: string;
424
+ "3-4"?: string;
425
+ "3-5"?: string;
426
+ "3-6"?: string;
427
+ "4-5"?: string;
428
+ "4-6"?: string;
429
+ "5-6"?: string;
430
+ });
431
+ }];
396
432
  type MarkdownPreferencesNoMultipleEmptyLines = [] | [{
397
433
  max?: number;
398
434
  maxEOF?: number;
399
435
  maxBOF?: number;
400
436
  }];
437
+ type MarkdownPreferencesNoTabs = [] | [{
438
+ checkTarget?: ("all" | "indentation" | "non-indentation");
439
+ ignoreCodeBlocks?: string[];
440
+ codeBlockTabWidth?: number;
441
+ }];
401
442
  type MarkdownPreferencesNoTrailingSpaces = [] | [{
402
443
  skipBlankLines?: boolean;
403
444
  ignoreComments?: boolean;
@@ -543,7 +584,7 @@ declare namespace meta_d_exports {
543
584
  export { name, version };
544
585
  }
545
586
  declare const name: "eslint-plugin-markdown-preferences";
546
- declare const version: "0.36.3";
587
+ declare const version: "0.38.0";
547
588
  //#endregion
548
589
  //#region src/language/ast-types.d.ts
549
590
  type Node = mdast.Node;
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as __export } from "./chunk-Bp6m_JJh.js";
1
+ import { t as __export } from "./chunk-BAz01cYq.js";
2
2
  import stringWidth from "string-width";
3
3
  import emojiRegex from "emoji-regex-xs";
4
4
  import path from "node:path";
@@ -732,7 +732,7 @@ function getOtherMarker(unavailableMarker) {
732
732
  /**
733
733
  * Parse rule options.
734
734
  */
735
- function parseOptions$7(options) {
735
+ function parseOptions$8(options) {
736
736
  const primary = options.primary || "-";
737
737
  const secondary = options.secondary || getOtherMarker(primary);
738
738
  if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
@@ -800,7 +800,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
800
800
  },
801
801
  create(context) {
802
802
  const sourceCode = context.sourceCode;
803
- const options = parseOptions$7(context.options[0] || {});
803
+ const options = parseOptions$8(context.options[0] || {});
804
804
  let containerStack = {
805
805
  node: sourceCode.ast,
806
806
  level: 1,
@@ -1418,7 +1418,7 @@ var custom_container_marker_spacing_default = createRule("custom-container-marke
1418
1418
  /**
1419
1419
  * Parse options with defaults.
1420
1420
  */
1421
- function parseOptions$6(options) {
1421
+ function parseOptions$7(options) {
1422
1422
  return {
1423
1423
  linkDefinitionPlacement: {
1424
1424
  referencedFromSingleSection: options?.linkDefinitionPlacement?.referencedFromSingleSection || "document-last",
@@ -1490,7 +1490,7 @@ var definitions_last_default = createRule("definitions-last", {
1490
1490
  },
1491
1491
  create(context) {
1492
1492
  const sourceCode = context.sourceCode;
1493
- const options = parseOptions$6(context.options[0]);
1493
+ const options = parseOptions$7(context.options[0]);
1494
1494
  /**
1495
1495
  * Determine whether a node can be placed as the last node of the document or a section.
1496
1496
  */
@@ -5464,7 +5464,7 @@ function parseMathClosingSequenceFromText(text) {
5464
5464
  /**
5465
5465
  * Parse options.
5466
5466
  */
5467
- function parseOptions$5(options) {
5467
+ function parseOptions$6(options) {
5468
5468
  const listItems = options?.listItems;
5469
5469
  return { listItems: {
5470
5470
  first: listItems?.first ?? 1,
@@ -5517,7 +5517,7 @@ var indent_default = createRule("indent", {
5517
5517
  },
5518
5518
  create(context) {
5519
5519
  const sourceCode = context.sourceCode;
5520
- const options = parseOptions$5(context.options[0]);
5520
+ const options = parseOptions$6(context.options[0]);
5521
5521
  class AbsBlockStack {
5522
5522
  violations = [];
5523
5523
  getCurrentBlockquote() {
@@ -6723,11 +6723,11 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6723
6723
  },
6724
6724
  create(context) {
6725
6725
  const sourceCode = context.sourceCode;
6726
- const optionProvider = parseOptions$8(context.options[0]);
6726
+ const optionProvider = parseOptions$9(context.options[0]);
6727
6727
  /**
6728
6728
  * Parse the options.
6729
6729
  */
6730
- function parseOptions$8(option) {
6730
+ function parseOptions$9(option) {
6731
6731
  const newline = option?.newline ?? "never";
6732
6732
  const multiline = option?.multiline ?? false;
6733
6733
  return (bracketsRange) => {
@@ -6869,7 +6869,7 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6869
6869
  /**
6870
6870
  * The basic option for links and images.
6871
6871
  */
6872
- function parseOptions$4(option) {
6872
+ function parseOptions$5(option) {
6873
6873
  const space = option?.space ?? "never";
6874
6874
  const imagesInLinks = option?.imagesInLinks;
6875
6875
  return {
@@ -6919,7 +6919,7 @@ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6919
6919
  },
6920
6920
  create(context) {
6921
6921
  const sourceCode = context.sourceCode;
6922
- const options = parseOptions$4(context.options[0]);
6922
+ const options = parseOptions$5(context.options[0]);
6923
6923
  /**
6924
6924
  * Verify the space after the opening bracket and before the closing bracket.
6925
6925
  */
@@ -7228,11 +7228,11 @@ var link_paren_newline_default = createRule("link-paren-newline", {
7228
7228
  },
7229
7229
  create(context) {
7230
7230
  const sourceCode = context.sourceCode;
7231
- const optionProvider = parseOptions$8(context.options[0]);
7231
+ const optionProvider = parseOptions$9(context.options[0]);
7232
7232
  /**
7233
7233
  * Parse the options.
7234
7234
  */
7235
- function parseOptions$8(option) {
7235
+ function parseOptions$9(option) {
7236
7236
  const newline = option?.newline ?? "never";
7237
7237
  const multiline = option?.multiline ?? false;
7238
7238
  return (openingParenIndex, closingParenIndex) => {
@@ -7690,6 +7690,153 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
7690
7690
  }
7691
7691
  });
7692
7692
 
7693
+ //#endregion
7694
+ //#region src/rules/no-heading-trailing-punctuation.ts
7695
+ const DEFAULT_PUNCTUATION = ".,;:!。、,;:!。、";
7696
+ const LEVEL_RANGE_MAP = {
7697
+ "1": [1, 1],
7698
+ "2": [2, 2],
7699
+ "3": [3, 3],
7700
+ "4": [4, 4],
7701
+ "5": [5, 5],
7702
+ "6": [6, 6],
7703
+ "1-2": [1, 2],
7704
+ "1-3": [1, 3],
7705
+ "1-4": [1, 4],
7706
+ "1-5": [1, 5],
7707
+ "1-6": [1, 6],
7708
+ "2-3": [2, 3],
7709
+ "2-4": [2, 4],
7710
+ "2-5": [2, 5],
7711
+ "2-6": [2, 6],
7712
+ "3-4": [3, 4],
7713
+ "3-5": [3, 5],
7714
+ "3-6": [3, 6],
7715
+ "4-5": [4, 5],
7716
+ "4-6": [4, 6],
7717
+ "5-6": [5, 6]
7718
+ };
7719
+ const HEADING_LEVELS = [
7720
+ 1,
7721
+ 2,
7722
+ 3,
7723
+ 4,
7724
+ 5,
7725
+ 6
7726
+ ];
7727
+ /**
7728
+ * Parse heading level ranges and build a map of level -> punctuation string
7729
+ */
7730
+ function buildPunctuationMap(option, segmenter$1) {
7731
+ if (typeof option === "string" || option === void 0) {
7732
+ const punctuation = option ?? DEFAULT_PUNCTUATION;
7733
+ const chars = new Set([...segmenter$1.segment(punctuation)].map((s) => s.segment));
7734
+ return Object.fromEntries(HEADING_LEVELS.map((level) => [level, chars]));
7735
+ }
7736
+ const defaultPunctuation = option.default ?? DEFAULT_PUNCTUATION;
7737
+ const defaultChars = new Set([...segmenter$1.segment(defaultPunctuation)].map((s) => s.segment));
7738
+ const map = Object.fromEntries(HEADING_LEVELS.map((level) => [level, defaultChars]));
7739
+ for (const [key, value] of Object.entries(option)) {
7740
+ if (key === "default" || value == null) continue;
7741
+ const range = LEVEL_RANGE_MAP[key];
7742
+ if (!range) continue;
7743
+ const chars = new Set([...segmenter$1.segment(value)].map((s) => s.segment));
7744
+ for (let level = range[0]; level <= range[1]; level++) map[level] = chars;
7745
+ }
7746
+ return map;
7747
+ }
7748
+ var no_heading_trailing_punctuation_default = createRule("no-heading-trailing-punctuation", {
7749
+ meta: {
7750
+ type: "suggestion",
7751
+ docs: {
7752
+ description: "disallow trailing punctuation in headings.",
7753
+ categories: [],
7754
+ listCategory: "Preference"
7755
+ },
7756
+ hasSuggestions: true,
7757
+ schema: [{
7758
+ type: "object",
7759
+ properties: { punctuation: { oneOf: [{
7760
+ type: "string",
7761
+ description: "String of punctuation characters to disallow at the end of headings."
7762
+ }, {
7763
+ type: "object",
7764
+ properties: {
7765
+ default: {
7766
+ type: "string",
7767
+ description: "Default punctuation characters for all heading levels."
7768
+ },
7769
+ ...Object.fromEntries(Object.entries(LEVEL_RANGE_MAP).map(([key, [start, end]]) => [key, {
7770
+ type: "string",
7771
+ description: start === end ? `Punctuation characters for heading level ${start}.` : `Punctuation characters for heading levels ${start} to ${end}.`
7772
+ }]))
7773
+ },
7774
+ additionalProperties: false
7775
+ }] } },
7776
+ additionalProperties: false
7777
+ }],
7778
+ messages: {
7779
+ trailingPunctuation: "Headings should not end with punctuation '{{char}}'.",
7780
+ removePunctuation: "Remove trailing punctuation '{{char}}'."
7781
+ }
7782
+ },
7783
+ create(context) {
7784
+ const sourceCode = context.sourceCode;
7785
+ const segmenter$1 = new Intl.Segmenter("en", { granularity: "grapheme" });
7786
+ const punctuationMap = buildPunctuationMap(context.options[0]?.punctuation, segmenter$1);
7787
+ return { heading(node) {
7788
+ if (!node.children.length) return;
7789
+ const punctuationChars = punctuationMap[node.depth];
7790
+ if (punctuationChars.size === 0) return;
7791
+ const lastTextNode = findLastTextNode(node.children);
7792
+ if (!lastTextNode) return;
7793
+ const trimmedText = sourceCode.getText(lastTextNode).trimEnd();
7794
+ if (!trimmedText.length) return;
7795
+ const segments = [...segmenter$1.segment(trimmedText)];
7796
+ const lastSegment = segments[segments.length - 1];
7797
+ if (!lastSegment) return;
7798
+ const lastChar = lastSegment.segment;
7799
+ if (!punctuationChars.has(lastChar)) return;
7800
+ const [nodeStart] = sourceCode.getRange(lastTextNode);
7801
+ const punctuationIndex = nodeStart + lastSegment.index;
7802
+ context.report({
7803
+ node: lastTextNode,
7804
+ messageId: "trailingPunctuation",
7805
+ data: { char: lastChar },
7806
+ loc: {
7807
+ start: sourceCode.getLocFromIndex(punctuationIndex),
7808
+ end: sourceCode.getLocFromIndex(punctuationIndex + lastChar.length)
7809
+ },
7810
+ suggest: [{
7811
+ messageId: "removePunctuation",
7812
+ data: { char: lastChar },
7813
+ fix(fixer) {
7814
+ return fixer.removeRange([punctuationIndex, punctuationIndex + lastChar.length]);
7815
+ }
7816
+ }]
7817
+ });
7818
+ } };
7819
+ /**
7820
+ * Find the last text node within a node's children.
7821
+ * Recursively traverses nested structures (emphasis, strong, link, etc.).
7822
+ */
7823
+ function findLastTextNode(children) {
7824
+ for (let i = children.length - 1; i >= 0; i--) {
7825
+ const child = children[i];
7826
+ if (child.type === "text") {
7827
+ if (child.value.trim()) return child;
7828
+ continue;
7829
+ }
7830
+ if ("children" in child && Array.isArray(child.children)) {
7831
+ const nestedLast = findLastTextNode(child.children);
7832
+ if (nestedLast) return nestedLast;
7833
+ }
7834
+ }
7835
+ return null;
7836
+ }
7837
+ }
7838
+ });
7839
+
7693
7840
  //#endregion
7694
7841
  //#region src/rules/no-implicit-block-closing.ts
7695
7842
  var no_implicit_block_closing_default = createRule("no-implicit-block-closing", {
@@ -8486,6 +8633,161 @@ var no_multiple_empty_lines_default = createRule("no-multiple-empty-lines", {
8486
8633
  }
8487
8634
  });
8488
8635
 
8636
+ //#endregion
8637
+ //#region src/rules/no-tabs.ts
8638
+ /**
8639
+ * Parse options
8640
+ */
8641
+ function parseOptions$4(options) {
8642
+ const checkTarget = options?.checkTarget || "all";
8643
+ const checkTargets = checkTarget === "all" ? ["indentation", "non-indentation"] : [checkTarget];
8644
+ return {
8645
+ ignoreCodeBlocks: options?.ignoreCodeBlocks || [],
8646
+ checkTargets,
8647
+ codeBlockTabWidth: options?.codeBlockTabWidth ?? 4
8648
+ };
8649
+ }
8650
+ var no_tabs_default = createRule("no-tabs", {
8651
+ meta: {
8652
+ type: "layout",
8653
+ docs: {
8654
+ description: "disallow tab characters in Markdown files.",
8655
+ categories: ["standard"],
8656
+ listCategory: "Whitespace"
8657
+ },
8658
+ fixable: "whitespace",
8659
+ hasSuggestions: false,
8660
+ schema: [{
8661
+ type: "object",
8662
+ properties: {
8663
+ checkTarget: {
8664
+ type: "string",
8665
+ enum: [
8666
+ "all",
8667
+ "indentation",
8668
+ "non-indentation"
8669
+ ],
8670
+ default: "all"
8671
+ },
8672
+ ignoreCodeBlocks: {
8673
+ type: "array",
8674
+ items: { type: "string" },
8675
+ default: []
8676
+ },
8677
+ codeBlockTabWidth: {
8678
+ type: "integer",
8679
+ minimum: 1,
8680
+ default: 4
8681
+ }
8682
+ },
8683
+ additionalProperties: false
8684
+ }],
8685
+ messages: { unexpectedTab: "Unexpected tab character." }
8686
+ },
8687
+ create(context) {
8688
+ const sourceCode = context.sourceCode;
8689
+ const { ignoreCodeBlocks, checkTargets, codeBlockTabWidth } = parseOptions$4(context.options[0]);
8690
+ const codeBlocks = [];
8691
+ /** Check if a language should be ignored based on ignoreCodeBlocks option */
8692
+ function shouldIgnoreLanguage(language$2) {
8693
+ if (ignoreCodeBlocks.length === 0) return false;
8694
+ if (ignoreCodeBlocks.includes("*")) return true;
8695
+ return language$2 !== null && ignoreCodeBlocks.includes(language$2);
8696
+ }
8697
+ /**
8698
+ * Check if the given index is at indentation (beginning of line)
8699
+ */
8700
+ function isAtIndentation(index) {
8701
+ const loc = sourceCode.getLocFromIndex(index);
8702
+ const text = sourceCode.lines[loc.line - 1].slice(0, loc.column - 1);
8703
+ return !text || isWhitespace(text);
8704
+ }
8705
+ /**
8706
+ * Find the code block containing the given index
8707
+ */
8708
+ function findCodeBlock(index) {
8709
+ const codeBlock = codeBlocks.find((block) => index >= block.contentRange[0] && index < block.contentRange[1]);
8710
+ if (!codeBlock) return null;
8711
+ const loc = sourceCode.getLocFromIndex(index);
8712
+ if (getWidth(sourceCode.lines[loc.line - 1].slice(0, loc.column - 1)) < codeBlock.indentWidth) return null;
8713
+ return codeBlock;
8714
+ }
8715
+ /**
8716
+ * Calculate the number of spaces to replace a tab.
8717
+ * Handles both regular tabs and tabs within code block content.
8718
+ */
8719
+ function calculateReplacementSpaces(tabIndex, codeBlock) {
8720
+ const loc = sourceCode.getLocFromIndex(tabIndex);
8721
+ const beforeText = sourceCode.lines[loc.line - 1].slice(0, loc.column - 1);
8722
+ if (!codeBlock || codeBlockTabWidth === 4) return 4 - getWidth(beforeText) % 4;
8723
+ let tabStop = codeBlock.indentWidth <= 0 ? codeBlockTabWidth : 4;
8724
+ let beforeWidth = 0;
8725
+ for (const c of beforeText) {
8726
+ if (c === " ") beforeWidth += tabStop - beforeWidth % tabStop;
8727
+ else beforeWidth++;
8728
+ if (tabStop === 4 && codeBlock.indentWidth <= beforeWidth) tabStop = codeBlockTabWidth;
8729
+ }
8730
+ return tabStop - beforeWidth % tabStop;
8731
+ }
8732
+ return {
8733
+ code(node) {
8734
+ const range = sourceCode.getRange(node);
8735
+ const loc = sourceCode.getLoc(node);
8736
+ const codeBlockStartLineText = sourceCode.lines[loc.start.line - 1];
8737
+ const codeBlockIndentText = codeBlockStartLineText.slice(0, loc.start.column - 1);
8738
+ if (getCodeBlockKind(sourceCode, node) === "indented") {
8739
+ const baseWidth = getWidth(codeBlockIndentText);
8740
+ let indentText;
8741
+ let indentColumnIndex = loc.start.column;
8742
+ while (getWidth(indentText = codeBlockStartLineText.slice(0, indentColumnIndex)) < baseWidth + 4) indentColumnIndex++;
8743
+ codeBlocks.push({
8744
+ contentRange: [range[0] + 1, range[1]],
8745
+ indentWidth: getWidth(indentText),
8746
+ ignore: shouldIgnoreLanguage(null) ? "check" : indentText.includes(" ") ? "fix" : false
8747
+ });
8748
+ return;
8749
+ }
8750
+ const parsed = parseFencedCodeBlock(sourceCode, node);
8751
+ if (!parsed) {
8752
+ codeBlocks.push({
8753
+ contentRange: range,
8754
+ indentWidth: getWidth(codeBlockIndentText),
8755
+ ignore: "check"
8756
+ });
8757
+ return;
8758
+ }
8759
+ codeBlocks.push({
8760
+ contentRange: [(parsed.meta ?? parsed.language ?? parsed.openingFence).range[1], parsed.closingFence?.range[0] ?? range[1]],
8761
+ indentWidth: getWidth(codeBlockIndentText),
8762
+ ignore: shouldIgnoreLanguage(node.lang?.toLowerCase() ?? null) ? "check" : false
8763
+ });
8764
+ },
8765
+ "root:exit"() {
8766
+ for (let i = 0; i < sourceCode.text.length; i++) {
8767
+ if (sourceCode.text[i] !== " ") continue;
8768
+ if (isAtIndentation(i)) {
8769
+ if (!checkTargets.includes("indentation")) continue;
8770
+ } else if (!checkTargets.includes("non-indentation")) continue;
8771
+ const codeBlock = findCodeBlock(i);
8772
+ if (codeBlock?.ignore === "check") continue;
8773
+ context.report({
8774
+ loc: {
8775
+ start: sourceCode.getLocFromIndex(i),
8776
+ end: sourceCode.getLocFromIndex(i + 1)
8777
+ },
8778
+ messageId: "unexpectedTab",
8779
+ fix: (fixer) => {
8780
+ if (codeBlock?.ignore === "fix") return null;
8781
+ const spaces = calculateReplacementSpaces(i, codeBlock);
8782
+ return fixer.replaceTextRange([i, i + 1], " ".repeat(spaces));
8783
+ }
8784
+ });
8785
+ }
8786
+ }
8787
+ };
8788
+ }
8789
+ });
8790
+
8489
8791
  //#endregion
8490
8792
  //#region src/rules/no-text-backslash-linebreak.ts
8491
8793
  var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebreak", {
@@ -12105,10 +12407,12 @@ const rules$1 = [
12105
12407
  link_paren_spacing_default,
12106
12408
  link_title_style_default,
12107
12409
  list_marker_alignment_default,
12410
+ no_heading_trailing_punctuation_default,
12108
12411
  no_implicit_block_closing_default,
12109
12412
  no_laziness_blockquotes_default,
12110
12413
  no_multi_spaces_default,
12111
12414
  no_multiple_empty_lines_default,
12415
+ no_tabs_default,
12112
12416
  no_text_backslash_linebreak_default,
12113
12417
  no_trailing_spaces_default,
12114
12418
  ordered_list_marker_sequence_default,
@@ -12209,6 +12513,7 @@ const rules$2 = {
12209
12513
  "markdown-preferences/no-laziness-blockquotes": "error",
12210
12514
  "markdown-preferences/no-multi-spaces": "error",
12211
12515
  "markdown-preferences/no-multiple-empty-lines": "error",
12516
+ "markdown-preferences/no-tabs": "error",
12212
12517
  "markdown-preferences/no-text-backslash-linebreak": "error",
12213
12518
  "markdown-preferences/no-trailing-spaces": "error",
12214
12519
  "markdown-preferences/ordered-list-marker-sequence": "error",
@@ -12236,7 +12541,7 @@ var meta_exports = /* @__PURE__ */ __export({
12236
12541
  version: () => version
12237
12542
  });
12238
12543
  const name = "eslint-plugin-markdown-preferences";
12239
- const version = "0.36.3";
12544
+ const version = "0.38.0";
12240
12545
 
12241
12546
  //#endregion
12242
12547
  //#region src/language/extensions/micromark-custom-container.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.36.3",
3
+ "version": "0.38.0",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {
@@ -80,7 +80,7 @@
80
80
  "@changesets/changelog-github": "^0.5.1",
81
81
  "@changesets/cli": "^2.28.1",
82
82
  "@changesets/get-release-plan": "^4.0.8",
83
- "@eslint/core": "^0.17.0",
83
+ "@eslint/core": "^1.0.0",
84
84
  "@eslint/markdown": "^7.4.0",
85
85
  "@ota-meshi/eslint-plugin": "^0.18.0",
86
86
  "@shikijs/vitepress-twoslash": "^3.0.0",
@@ -114,7 +114,7 @@
114
114
  "eslint-plugin-yml": "^1.17.0",
115
115
  "eslint-snapshot-rule-tester": "^0.1.0",
116
116
  "eslint-typegen": "^2.0.0",
117
- "espree": "^10.3.0",
117
+ "espree": "^11.0.0",
118
118
  "events": "^3.3.0",
119
119
  "globals": "^16.0.0",
120
120
  "mocha": "^11.1.0",
@@ -126,7 +126,7 @@
126
126
  "stylelint-config-recommended-vue": "^1.6.0",
127
127
  "stylelint-config-standard": "^39.0.0",
128
128
  "stylelint-config-standard-vue": "^1.0.0",
129
- "tsdown": "^0.16.0",
129
+ "tsdown": "^0.17.0",
130
130
  "tsx": "^4.19.3",
131
131
  "twoslash-eslint": "^0.3.1",
132
132
  "type-fest": "^5.0.0",
@@ -1,13 +0,0 @@
1
- //#region rolldown:runtime
2
- var __defProp = Object.defineProperty;
3
- var __export = (all) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- return target;
10
- };
11
-
12
- //#endregion
13
- export { __export as t };