eslint-plugin-markdown-preferences 0.28.0 β†’ 0.29.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/README.md CHANGED
@@ -13,17 +13,15 @@ A specialized ESLint plugin that helps enforce consistent writing style and form
13
13
 
14
14
  ## πŸ“› Features
15
15
 
16
- - πŸ“ **Comprehensive style enforcement**\
17
- Unifies document expression and description style: heading casing, table header casing, inline code/link usage, emoji notation, and more.
18
- - 🧩 **Notation and formatting consistency**\
19
- Standardizes Markdown notation: list markers, code fences, link/reference style, thematic breaks, and table formatting.
20
- - 🎨 **Whitespace and decorative rules**\
21
- Controls indentation, spacing, line breaks, trailing spaces, and decorative elements for clean, readable Markdown.
22
- - πŸ”§ **Auto-fix support**\
23
- Most rules support ESLint's `--fix` option for effortless formatting and correction.
24
- - βš™οΈ **Flexible configuration**\
25
- Provides both "recommended" and "standard" configs, and allows you to finely customize formatting and rules to suit your preferences and Markdown style.
26
- - 🌐 **Live demo & documentation**\
16
+ - πŸ“ **Comprehensive style enforcement**
17
+ Enforces consistent heading casing, table header casing, inline code/link usage, emoji notation, and more for unified document style.
18
+ - 🧩 **Powerful formatting consistency**
19
+ Strongly standardizes Markdown formatting: whitespace, indentation, spacing, line breaks, list markers, code fences, links, references, thematic breaks, tables, and decorative elementsβ€”ensuring documents are clean and uniform.
20
+ - πŸš€ **Extended Markdown syntax support**
21
+ Supports custom containers, mathematical expressions, and other extended syntax for high compatibility with VitePress.
22
+ - πŸ”§ **Auto-fix support**
23
+ Most rules support ESLint's `--fix` option for automatic correction.
24
+ - 🌐 **Online Demo & Documentation**
27
25
  Try it instantly in the [Online Demo](https://eslint-online-playground.netlify.app/#eslint-plugin-markdown-preferences) and see [full documentation][documentation site].
28
26
 
29
27
  <!--DOCS_IGNORE_START-->
@@ -164,25 +162,25 @@ The rules with the following πŸ’„ are included in the `standard` config.
164
162
 
165
163
  <!-- prettier-ignore-start -->
166
164
 
167
- | Rule ID | Description | Fixable | Config |
168
- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- | :-----: | :----: |
169
- | [markdown-preferences/bullet-list-marker-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/bullet-list-marker-style.html) | enforce consistent bullet list (unordered list) marker style | πŸ”§ | πŸ’„ |
170
- | [markdown-preferences/code-fence-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-style.html) | enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks. | πŸ”§ | πŸ’„ |
171
- | [markdown-preferences/definitions-last](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html) | require link definitions and footnote definitions to be placed at the end of the document | πŸ”§ | |
172
- | [markdown-preferences/emphasis-delimiters-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emphasis-delimiters-style.html) | enforce a consistent delimiter style for emphasis and strong emphasis | πŸ”§ | πŸ’„ |
173
- | [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | πŸ”§ | β­πŸ’„ |
174
- | [markdown-preferences/level1-heading-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level1-heading-style.html) | enforce consistent style for level 1 headings | πŸ”§ | πŸ’„ |
175
- | [markdown-preferences/level2-heading-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level2-heading-style.html) | enforce consistent style for level 2 headings | πŸ”§ | πŸ’„ |
176
- | [markdown-preferences/link-destination-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-destination-style.html) | enforce a consistent style for link destinations | πŸ”§ | πŸ’„ |
177
- | [markdown-preferences/link-title-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-title-style.html) | enforce a consistent style for link titles | πŸ”§ | πŸ’„ |
178
- | [markdown-preferences/no-implicit-block-closing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-implicit-block-closing.html) | disallow implicit block closing for fenced code blocks, math blocks, and custom blocks | πŸ”§ | β­πŸ’„ |
179
- | [markdown-preferences/no-text-backslash-linebreak](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html) | disallow text backslash at the end of a line. | | β­πŸ’„ |
180
- | [markdown-preferences/ordered-list-marker-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-style.html) | enforce consistent ordered list marker style | πŸ”§ | πŸ’„ |
181
- | [markdown-preferences/prefer-autolinks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html) | enforce the use of autolinks for URLs | πŸ”§ | β­πŸ’„ |
182
- | [markdown-preferences/prefer-fenced-code-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-fenced-code-blocks.html) | enforce the use of fenced code blocks over indented code blocks | πŸ”§ | β­πŸ’„ |
183
- | [markdown-preferences/prefer-link-reference-definitions](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-link-reference-definitions.html) | enforce using link reference definitions instead of inline links | πŸ”§ | |
184
- | [markdown-preferences/strikethrough-delimiters-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/strikethrough-delimiters-style.html) | enforce a consistent delimiter style for strikethrough | πŸ”§ | πŸ’„ |
185
- | [markdown-preferences/thematic-break-character-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/thematic-break-character-style.html) | enforce consistent character style for thematic breaks (horizontal rules) in Markdown. | πŸ”§ | πŸ’„ |
165
+ | Rule ID | Description | Fixable | Config |
166
+ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | :-----: | :----: |
167
+ | [markdown-preferences/bullet-list-marker-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/bullet-list-marker-style.html) | enforce consistent bullet list (unordered list) marker style | πŸ”§ | πŸ’„ |
168
+ | [markdown-preferences/code-fence-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-style.html) | enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks. | πŸ”§ | πŸ’„ |
169
+ | [markdown-preferences/definitions-last](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html) | require link definitions and footnote definitions to be placed at the end of the document | πŸ”§ | |
170
+ | [markdown-preferences/emphasis-delimiters-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/emphasis-delimiters-style.html) | enforce a consistent delimiter style for emphasis and strong emphasis | πŸ”§ | πŸ’„ |
171
+ | [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | πŸ”§ | β­πŸ’„ |
172
+ | [markdown-preferences/level1-heading-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level1-heading-style.html) | enforce consistent style for level 1 headings | πŸ”§ | πŸ’„ |
173
+ | [markdown-preferences/level2-heading-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/level2-heading-style.html) | enforce consistent style for level 2 headings | πŸ”§ | πŸ’„ |
174
+ | [markdown-preferences/link-destination-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-destination-style.html) | enforce a consistent style for link destinations | πŸ”§ | πŸ’„ |
175
+ | [markdown-preferences/link-title-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-title-style.html) | enforce a consistent style for link titles | πŸ”§ | πŸ’„ |
176
+ | [markdown-preferences/no-implicit-block-closing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-implicit-block-closing.html) | disallow implicit block closing for fenced code blocks, math blocks, and custom containers | πŸ”§ | β­πŸ’„ |
177
+ | [markdown-preferences/no-text-backslash-linebreak](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html) | disallow text backslash at the end of a line. | | β­πŸ’„ |
178
+ | [markdown-preferences/ordered-list-marker-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-style.html) | enforce consistent ordered list marker style | πŸ”§ | πŸ’„ |
179
+ | [markdown-preferences/prefer-autolinks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html) | enforce the use of autolinks for URLs | πŸ”§ | β­πŸ’„ |
180
+ | [markdown-preferences/prefer-fenced-code-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-fenced-code-blocks.html) | enforce the use of fenced code blocks over indented code blocks | πŸ”§ | β­πŸ’„ |
181
+ | [markdown-preferences/prefer-link-reference-definitions](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-link-reference-definitions.html) | enforce using link reference definitions instead of inline links | πŸ”§ | |
182
+ | [markdown-preferences/strikethrough-delimiters-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/strikethrough-delimiters-style.html) | enforce a consistent delimiter style for strikethrough | πŸ”§ | πŸ’„ |
183
+ | [markdown-preferences/thematic-break-character-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/thematic-break-character-style.html) | enforce consistent character style for thematic breaks (horizontal rules) in Markdown. | πŸ”§ | πŸ’„ |
186
184
 
187
185
  <!-- prettier-ignore-end -->
188
186
 
@@ -204,6 +202,7 @@ The rules with the following πŸ’„ are included in the `standard` config.
204
202
  | [markdown-preferences/no-multi-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multi-spaces.html) | disallow multiple spaces | πŸ”§ | πŸ’„ |
205
203
  | [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. | πŸ”§ | πŸ’„ |
206
204
  | [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. | πŸ”§ | πŸ’„ |
205
+ | [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 | πŸ”§ | πŸ’„ |
207
206
  | [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 | πŸ”§ | πŸ’„ |
208
207
  | [markdown-preferences/table-pipe-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-pipe-spacing.html) | enforce consistent spacing around table pipes | πŸ”§ | πŸ’„ |
209
208
 
package/lib/index.d.ts CHANGED
@@ -126,7 +126,7 @@ interface RuleOptions {
126
126
  */
127
127
  'markdown-preferences/list-marker-alignment'?: Linter.RuleEntry<MarkdownPreferencesListMarkerAlignment>;
128
128
  /**
129
- * disallow implicit block closing for fenced code blocks, math blocks, and custom blocks
129
+ * disallow implicit block closing for fenced code blocks, math blocks, and custom containers
130
130
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-implicit-block-closing.html
131
131
  */
132
132
  'markdown-preferences/no-implicit-block-closing'?: Linter.RuleEntry<[]>;
@@ -170,6 +170,11 @@ interface RuleOptions {
170
170
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-style.html
171
171
  */
172
172
  'markdown-preferences/ordered-list-marker-style'?: Linter.RuleEntry<MarkdownPreferencesOrderedListMarkerStyle>;
173
+ /**
174
+ * disallow or require padding inside custom containers
175
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padded-custom-containers.html
176
+ */
177
+ 'markdown-preferences/padded-custom-containers'?: Linter.RuleEntry<MarkdownPreferencesPaddedCustomContainers>;
173
178
  /**
174
179
  * require or disallow padding lines between blocks
175
180
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html
@@ -389,6 +394,9 @@ type MarkdownPreferencesOrderedListMarkerStyle = [] | [{
389
394
  prefer?: ("n." | "n)");
390
395
  }[];
391
396
  }];
397
+ type MarkdownPreferencesPaddedCustomContainers = [] | [{
398
+ padding?: ("always" | "never");
399
+ }];
392
400
  type MarkdownPreferencesPaddingLineBetweenBlocks = {
393
401
  prev: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "custom-container" | "math" | "import-code-snippet" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "custom-container" | "math" | "import-code-snippet" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "custom-container" | "math" | "import-code-snippet" | "*"))[]] | {
394
402
  type: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "custom-container" | "math" | "import-code-snippet" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "custom-container" | "math" | "import-code-snippet" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "custom-container" | "math" | "import-code-snippet" | "*"))[]]);
@@ -508,7 +516,7 @@ declare namespace meta_d_exports {
508
516
  export { name, version };
509
517
  }
510
518
  declare const name: "eslint-plugin-markdown-preferences";
511
- declare const version: "0.28.0";
519
+ declare const version: "0.29.1";
512
520
  //#endregion
513
521
  //#region src/language/ast-types.d.ts
514
522
  type Node = mdast.Node;
package/lib/index.js CHANGED
@@ -7617,7 +7617,7 @@ var no_implicit_block_closing_default = createRule("no-implicit-block-closing",
7617
7617
  meta: {
7618
7618
  type: "suggestion",
7619
7619
  docs: {
7620
- description: "disallow implicit block closing for fenced code blocks, math blocks, and custom blocks",
7620
+ description: "disallow implicit block closing for fenced code blocks, math blocks, and custom containers",
7621
7621
  categories: ["recommended", "standard"],
7622
7622
  listCategory: "Notation"
7623
7623
  },
@@ -7636,7 +7636,8 @@ var no_implicit_block_closing_default = createRule("no-implicit-block-closing",
7636
7636
  code(node) {
7637
7637
  const parsed = parseFencedCodeBlock(sourceCode, node);
7638
7638
  if (!parsed) return;
7639
- if (!parsed.closingFence) context.report({
7639
+ if (parsed.closingFence) return;
7640
+ context.report({
7640
7641
  node,
7641
7642
  loc: parsed.openingFence.loc,
7642
7643
  messageId: "missingClosingFence",
@@ -7651,7 +7652,8 @@ var no_implicit_block_closing_default = createRule("no-implicit-block-closing",
7651
7652
  math(node) {
7652
7653
  const parsed = parseMathBlock(sourceCode, node);
7653
7654
  if (!parsed) return;
7654
- if (!parsed.closingSequence) context.report({
7655
+ if (parsed.closingSequence) return;
7656
+ context.report({
7655
7657
  node,
7656
7658
  loc: parsed.openingSequence.loc,
7657
7659
  messageId: "missingClosingMath",
@@ -7666,11 +7668,13 @@ var no_implicit_block_closing_default = createRule("no-implicit-block-closing",
7666
7668
  customContainer(node) {
7667
7669
  const parsed = parseCustomContainer(sourceCode, node);
7668
7670
  if (!parsed) return;
7669
- if (!parsed.closingSequence) context.report({
7671
+ if (parsed.closingSequence) return;
7672
+ context.report({
7670
7673
  node,
7671
7674
  loc: parsed.openingSequence.loc,
7672
7675
  messageId: "missingClosingContainer",
7673
7676
  fix(fixer) {
7677
+ if (withinSameLengthOpeningCustomContainer(sourceCode, node, parsed.openingSequence.text.length)) return null;
7674
7678
  const openingLoc = parsed.openingSequence.loc;
7675
7679
  const prefix = sourceCode.lines[openingLoc.start.line - 1].slice(0, openingLoc.start.column - 1).replace(/[^\s>]/gu, " ");
7676
7680
  const closingSequence = parsed.openingSequence.text;
@@ -7681,6 +7685,23 @@ var no_implicit_block_closing_default = createRule("no-implicit-block-closing",
7681
7685
  };
7682
7686
  }
7683
7687
  });
7688
+ /**
7689
+ * Check if the given custom container node is within another custom container
7690
+ * that has the same length of opening sequence.
7691
+ *
7692
+ */
7693
+ function withinSameLengthOpeningCustomContainer(sourceCode, node, openingLength) {
7694
+ let current = getParent(sourceCode, node);
7695
+ while (current) {
7696
+ if (current.type === "customContainer") {
7697
+ const parsed = parseCustomContainer(sourceCode, current);
7698
+ if (!parsed) return true;
7699
+ if (parsed.openingSequence.text.length === openingLength) return true;
7700
+ }
7701
+ current = getParent(sourceCode, current);
7702
+ }
7703
+ return false;
7704
+ }
7684
7705
 
7685
7706
  //#endregion
7686
7707
  //#region src/rules/no-laziness-blockquotes.ts
@@ -8922,6 +8943,101 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
8922
8943
  }
8923
8944
  });
8924
8945
 
8946
+ //#endregion
8947
+ //#region src/rules/padded-custom-containers.ts
8948
+ var padded_custom_containers_default = createRule("padded-custom-containers", {
8949
+ meta: {
8950
+ type: "layout",
8951
+ docs: {
8952
+ description: "disallow or require padding inside custom containers",
8953
+ categories: ["standard"],
8954
+ listCategory: "Whitespace"
8955
+ },
8956
+ fixable: "whitespace",
8957
+ hasSuggestions: false,
8958
+ schema: [{
8959
+ type: "object",
8960
+ properties: { padding: {
8961
+ type: "string",
8962
+ enum: ["always", "never"],
8963
+ default: "never"
8964
+ } },
8965
+ additionalProperties: false
8966
+ }],
8967
+ messages: {
8968
+ expectedPaddingAfterOpeningMarker: "Expected padding after opening marker.",
8969
+ expectedPaddingBeforeClosingMarker: "Expected padding before closing marker.",
8970
+ unexpectedPaddingAfterOpeningMarker: "Unexpected padding after opening marker.",
8971
+ unexpectedPaddingBeforeClosingMarker: "Unexpected padding before closing marker."
8972
+ }
8973
+ },
8974
+ create(context) {
8975
+ const sourceCode = context.sourceCode;
8976
+ const padding = (context.options[0] || {}).padding || "never";
8977
+ return { customContainer(node) {
8978
+ if (node.children.length === 0) return;
8979
+ const parsed = parseCustomContainer(sourceCode, node);
8980
+ if (!parsed) return;
8981
+ const { openingSequence, closingSequence } = parsed;
8982
+ const firstChild = node.children[0];
8983
+ const lastChild = node.children[node.children.length - 1];
8984
+ const firstChildLoc = sourceCode.getLoc(firstChild);
8985
+ const lastChildLoc = sourceCode.getLoc(lastChild);
8986
+ const paddingAfterOpeningLines = firstChildLoc.start.line - openingSequence.loc.end.line - 1;
8987
+ const paddingBeforeClosingLines = closingSequence ? closingSequence.loc.start.line - lastChildLoc.end.line - 1 : null;
8988
+ if (padding === "always") {
8989
+ if (paddingAfterOpeningLines <= 0) {
8990
+ const reportEndToken = parsed.info ?? openingSequence;
8991
+ const reportRange = [openingSequence.range[0], reportEndToken.range[1]];
8992
+ context.report({
8993
+ messageId: "expectedPaddingAfterOpeningMarker",
8994
+ loc: getSourceLocationFromRange(sourceCode, node, reportRange),
8995
+ fix(fixer) {
8996
+ return fixer.insertTextAfterRange(reportRange, "\n");
8997
+ }
8998
+ });
8999
+ }
9000
+ if (closingSequence && paddingBeforeClosingLines !== null && paddingBeforeClosingLines <= 0) context.report({
9001
+ messageId: "expectedPaddingBeforeClosingMarker",
9002
+ loc: getSourceLocationFromRange(sourceCode, node, closingSequence.range),
9003
+ fix(fixer) {
9004
+ return fixer.insertTextBeforeRange([closingSequence.range[0] - closingSequence.loc.start.column + 1, closingSequence.range[1]], "\n");
9005
+ }
9006
+ });
9007
+ } else if (padding === "never") {
9008
+ if (paddingAfterOpeningLines > 0) {
9009
+ const firstChildRange = sourceCode.getRange(firstChild);
9010
+ context.report({
9011
+ messageId: "unexpectedPaddingAfterOpeningMarker",
9012
+ loc: getSourceLocationFromRange(sourceCode, node, [openingSequence.range[1], firstChildRange[0]]),
9013
+ *fix(fixer) {
9014
+ const lines = getParsedLines(sourceCode);
9015
+ for (let lineNumber = openingSequence.loc.end.line + 1; lineNumber < firstChildLoc.start.line; lineNumber++) {
9016
+ const line = lines.get(lineNumber);
9017
+ yield fixer.removeRange(line.range);
9018
+ }
9019
+ }
9020
+ });
9021
+ }
9022
+ if (closingSequence && paddingBeforeClosingLines !== null && paddingBeforeClosingLines > 0) {
9023
+ const lastChildRange = sourceCode.getRange(lastChild);
9024
+ context.report({
9025
+ messageId: "unexpectedPaddingBeforeClosingMarker",
9026
+ loc: getSourceLocationFromRange(sourceCode, node, [lastChildRange[1], closingSequence.range[0]]),
9027
+ *fix(fixer) {
9028
+ const lines = getParsedLines(sourceCode);
9029
+ for (let lineNumber = lastChildLoc.end.line + 1; lineNumber < closingSequence.loc.start.line; lineNumber++) {
9030
+ const line = lines.get(lineNumber);
9031
+ yield fixer.removeRange(line.range);
9032
+ }
9033
+ }
9034
+ });
9035
+ }
9036
+ }
9037
+ } };
9038
+ }
9039
+ });
9040
+
8925
9041
  //#endregion
8926
9042
  //#region src/rules/padding-line-between-blocks.ts
8927
9043
  /**
@@ -10580,397 +10696,157 @@ var table_leading_trailing_pipes_default = createRule("table-leading-trailing-pi
10580
10696
  });
10581
10697
 
10582
10698
  //#endregion
10583
- //#region src/rules/table-pipe-alignment.ts
10584
- var table_pipe_alignment_default = createRule("table-pipe-alignment", {
10699
+ //#region src/rules/table-pipe-spacing.ts
10700
+ const currentOption = /* @__PURE__ */ new WeakMap();
10701
+ /**
10702
+ * Get the current options for the given source code.
10703
+ * This is a method that allows you to access the configuration of this rule from another rule.
10704
+ */
10705
+ function getCurrentTablePipeSpacingOption(sourceCode) {
10706
+ return currentOption.get(sourceCode) ?? null;
10707
+ }
10708
+ /**
10709
+ * Parsed options
10710
+ */
10711
+ function parseOptions(options) {
10712
+ const spaceOption = options?.space;
10713
+ const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10714
+ const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10715
+ const cellAlignOption = options?.cellAlign;
10716
+ const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10717
+ const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10718
+ const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10719
+ const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10720
+ return {
10721
+ leadingSpace,
10722
+ trailingSpace,
10723
+ cellAlignByDelimiter: {
10724
+ none: adjustAlign(defaultDelimiterCellAlign),
10725
+ left: adjustAlign(leftAlignmentDelimiterCellAlign),
10726
+ center: adjustAlign(centerAlignmentDelimiterCellAlign),
10727
+ right: adjustAlign(rightAlignmentDelimiterCellAlign)
10728
+ }
10729
+ };
10730
+ /**
10731
+ * Adjust the alignment option based on the spacing options.
10732
+ */
10733
+ function adjustAlign(align) {
10734
+ if (align === "left") {
10735
+ if (trailingSpace === "always") return "left";
10736
+ return "ignore";
10737
+ }
10738
+ if (align === "center") {
10739
+ if (leadingSpace === "always" && trailingSpace === "always") return "center";
10740
+ return "ignore";
10741
+ }
10742
+ if (align === "right") {
10743
+ if (leadingSpace === "always") return "right";
10744
+ return "ignore";
10745
+ }
10746
+ return align;
10747
+ }
10748
+ }
10749
+ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
10585
10750
  meta: {
10586
10751
  type: "layout",
10587
10752
  docs: {
10588
- description: "enforce consistent alignment of table pipes",
10753
+ description: "enforce consistent spacing around table pipes",
10589
10754
  categories: ["standard"],
10590
- listCategory: "Decorative"
10755
+ listCategory: "Whitespace"
10591
10756
  },
10592
- fixable: "code",
10757
+ fixable: "whitespace",
10593
10758
  hasSuggestions: false,
10594
10759
  schema: [{
10595
10760
  type: "object",
10596
- properties: { column: { enum: ["minimum", "consistent"] } },
10761
+ properties: {
10762
+ space: { anyOf: [{ enum: ["always", "never"] }, {
10763
+ type: "object",
10764
+ properties: {
10765
+ leading: { enum: ["always", "never"] },
10766
+ trailing: { enum: ["always", "never"] }
10767
+ },
10768
+ additionalProperties: false
10769
+ }] },
10770
+ cellAlign: { anyOf: [{ enum: [
10771
+ "left",
10772
+ "center",
10773
+ "right"
10774
+ ] }, {
10775
+ type: "object",
10776
+ properties: {
10777
+ defaultDelimiter: { enum: [
10778
+ "left",
10779
+ "center",
10780
+ "right",
10781
+ "ignore"
10782
+ ] },
10783
+ leftAlignmentDelimiter: { enum: [
10784
+ "left",
10785
+ "center",
10786
+ "right",
10787
+ "ignore"
10788
+ ] },
10789
+ centerAlignmentDelimiter: { enum: [
10790
+ "left",
10791
+ "center",
10792
+ "right",
10793
+ "ignore"
10794
+ ] },
10795
+ rightAlignmentDelimiter: { enum: [
10796
+ "left",
10797
+ "center",
10798
+ "right",
10799
+ "ignore"
10800
+ ] }
10801
+ },
10802
+ additionalProperties: false
10803
+ }] }
10804
+ },
10597
10805
  additionalProperties: false
10598
10806
  }],
10599
10807
  messages: {
10600
- addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
10601
- removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
10808
+ expectedSpaceBefore: "Expected 1 space before \"|\".",
10809
+ expectedNoSpaceBefore: "Expected no space before \"|\".",
10810
+ expectedSpaceAfter: "Expected 1 space after \"|\".",
10811
+ expectedNoSpaceAfter: "Expected no space after \"|\".",
10812
+ expectedAlignLeft: "Expected 1 space after \"|\" for left-aligned column.",
10813
+ expectedNoSpaceAlignLeft: "Expected no space after \"|\" for left-aligned column.",
10814
+ expectedAlignRight: "Expected 1 space before \"|\" for right-aligned column.",
10815
+ expectedNoSpaceAlignRight: "Expected no space before \"|\" for right-aligned column.",
10816
+ expectedAlignCenter: "Expected the number of spaces before and after the content to be the same or differ by 1 at most for center-aligned column."
10602
10817
  }
10603
10818
  },
10604
10819
  create(context) {
10605
10820
  const sourceCode = context.sourceCode;
10606
- const columnOption = (context.options[0] || {}).column || "minimum";
10607
- class TableContext {
10608
- rows;
10609
- columnCount;
10610
- _cacheHasSpaceBetweenContentAndTrailingPipe = /* @__PURE__ */ new Map();
10611
- _cacheExpectedPipePosition = /* @__PURE__ */ new Map();
10612
- constructor(parsed) {
10613
- const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
10614
- for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
10615
- this.rows = rows;
10616
- let columnCount = 0;
10617
- for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
10618
- this.columnCount = columnCount;
10619
- }
10620
- /**
10621
- * Get the expected pipe position for the index
10622
- */
10623
- getExpectedPipePosition(pipeIndex) {
10624
- let v = this._cacheExpectedPipePosition.get(pipeIndex);
10625
- if (v !== void 0) return v;
10626
- v = this._computeExpectedPipePositionWithoutCache(pipeIndex);
10627
- this._cacheExpectedPipePosition.set(pipeIndex, v);
10628
- return v;
10629
- }
10630
- /**
10631
- * Check if there is at least one space between content and trailing pipe
10632
- * for the index
10633
- *
10634
- * This is used to determine if the pipe should be aligned with a space before it.
10635
- */
10636
- hasSpaceBetweenContentAndTrailingPipe(pipeIndex) {
10637
- if (pipeIndex === 0) return false;
10638
- let v = this._cacheHasSpaceBetweenContentAndTrailingPipe.get(pipeIndex);
10639
- if (v != null) return v;
10640
- v = this._hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex);
10641
- this._cacheHasSpaceBetweenContentAndTrailingPipe.set(pipeIndex, v);
10642
- return v;
10643
- }
10644
- /**
10645
- * Get the expected pipe position for the index
10646
- */
10647
- _computeExpectedPipePositionWithoutCache(pipeIndex) {
10648
- if (pipeIndex === 0) {
10649
- const firstCell = this.rows[0].cells[0];
10650
- const firstToken = firstCell.leadingPipe ?? firstCell.content;
10651
- if (!firstToken) return null;
10652
- return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
10653
- }
10654
- if (columnOption === "minimum") return this.getMinimumPipePosition(pipeIndex);
10655
- else if (columnOption === "consistent") {
10656
- const columnIndex = pipeIndex - 1;
10657
- for (const row of this.rows) {
10658
- if (row.cells.length <= columnIndex) continue;
10659
- const cell = row.cells[columnIndex];
10660
- if (cell.type === "delimiter" || !cell.trailingPipe) continue;
10661
- const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
10662
- return Math.max(width, this.getMinimumPipePosition(pipeIndex) || 0);
10821
+ const options = parseOptions(context.options[0]);
10822
+ currentOption.set(sourceCode, options);
10823
+ /**
10824
+ * Verify for the leading pipe.
10825
+ */
10826
+ function verifyLeadingPipe(pipe, nextToken) {
10827
+ if (options.leadingSpace === "always") {
10828
+ if (pipe.range[1] < nextToken.range[0]) return true;
10829
+ context.report({
10830
+ loc: pipe.loc,
10831
+ messageId: "expectedSpaceAfter",
10832
+ fix(fixer) {
10833
+ return fixer.insertTextAfterRange(pipe.range, " ");
10663
10834
  }
10664
- }
10665
- return null;
10666
- }
10667
- /**
10668
- * Get the minimum pipe position for the index
10669
- */
10670
- getMinimumPipePosition(pipeIndex) {
10671
- const needSpaceBeforePipe = this.hasSpaceBetweenContentAndTrailingPipe(pipeIndex);
10672
- let maxWidth = 0;
10673
- const columnIndex = pipeIndex - 1;
10674
- for (const row of this.rows) {
10675
- if (row.cells.length <= columnIndex) continue;
10676
- const cell = row.cells[columnIndex];
10677
- let width;
10678
- if (cell.type === "delimiter") {
10679
- const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10680
- width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
10681
- } else {
10682
- if (!cell.content) continue;
10683
- width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
10684
- }
10685
- if (needSpaceBeforePipe) width += 1;
10686
- maxWidth = Math.max(maxWidth, width);
10687
- }
10688
- return maxWidth;
10689
- }
10690
- /**
10691
- * Check if there is at least one space between content and trailing pipe
10692
- */
10693
- _hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex) {
10694
- const columnIndex = pipeIndex - 1;
10695
- for (const row of this.rows) {
10696
- if (row.cells.length <= columnIndex) continue;
10697
- const cell = row.cells[columnIndex];
10698
- if (!cell.trailingPipe) continue;
10699
- let content;
10700
- if (cell.type === "delimiter") content = cell.delimiter;
10701
- else {
10702
- if (!cell.content) continue;
10703
- content = cell.content;
10704
- }
10705
- if (content.range[1] < cell.trailingPipe.range[0]) continue;
10706
- return false;
10707
- }
10708
- return true;
10709
- }
10710
- }
10711
- /**
10712
- * Verify the table pipes
10713
- */
10714
- function verifyTablePipes(table) {
10715
- const targetRows = [...table.rows];
10716
- for (const row of targetRows) for (let pipeIndex = 0; pipeIndex <= table.columnCount; pipeIndex++) if (!verifyRowPipe(row, pipeIndex, table)) break;
10717
- }
10718
- /**
10719
- * Verify the pipe in the row
10720
- */
10721
- function verifyRowPipe(row, pipeIndex, table) {
10722
- let cellIndex;
10723
- let pipe;
10724
- if (pipeIndex === 0) {
10725
- cellIndex = 0;
10726
- pipe = "leadingPipe";
10727
- } else {
10728
- cellIndex = pipeIndex - 1;
10729
- pipe = "trailingPipe";
10730
- }
10731
- if (row.cells.length <= cellIndex) return true;
10732
- const cell = row.cells[cellIndex];
10733
- const pipeToken = cell[pipe];
10734
- if (!pipeToken) return true;
10735
- return verifyPipe(pipeToken, pipeIndex, table, cell);
10736
- }
10737
- /**
10738
- * Verify the pipe position
10739
- */
10740
- function verifyPipe(pipe, pipeIndex, table, cell) {
10741
- const expected = table.getExpectedPipePosition(pipeIndex);
10742
- if (expected == null) return true;
10743
- const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
10744
- const diff = expected - actual;
10745
- if (diff === 0) return true;
10746
- context.report({
10747
- loc: pipe.loc,
10748
- messageId: diff > 0 ? "addSpaces" : "removeSpaces",
10749
- data: {
10750
- expected: String(expected),
10751
- count: String(Math.abs(diff)),
10752
- plural: Math.abs(diff) === 1 ? "" : "s"
10753
- },
10754
- fix(fixer) {
10755
- if (diff > 0) {
10756
- if (pipeIndex === 0 || cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
10757
- return fixer.insertTextAfterRange([cell.delimiter.range[0], cell.delimiter.range[0] + 1], "-".repeat(diff));
10758
- }
10759
- const baseEdit = fixRemoveSpaces();
10760
- if (baseEdit) return baseEdit;
10761
- if (pipeIndex === 0 || cell.type === "cell") return null;
10762
- const beforeDelimiter = sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1);
10763
- const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
10764
- const newLength = expected - widthBeforeDelimiter;
10765
- const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10766
- const spaceAfter = table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? " " : "";
10767
- if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
10768
- const delimiterPrefix = cell.align === "left" || cell.align === "center" ? ":" : "";
10769
- const delimiterSuffix = (cell.align === "right" || cell.align === "center" ? ":" : "") + spaceAfter;
10770
- const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
10771
- return fixer.replaceTextRange([cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
10772
- /**
10773
- * Fixer to remove spaces before the pipe
10774
- */
10775
- function fixRemoveSpaces() {
10776
- const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
10777
- const trimmedBeforePipe = beforePipe.trimEnd();
10778
- const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
10779
- const widthBeforePipe = getTextWidth(trimmedBeforePipe);
10780
- const newSpacesLength = expected - widthBeforePipe;
10781
- if (newSpacesLength < (table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? 1 : 0)) return null;
10782
- return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
10783
- }
10784
- }
10785
- });
10786
- return false;
10787
- }
10788
- /**
10789
- * Get the minimum delimiter length based on alignment
10790
- */
10791
- function getMinimumDelimiterLength(align) {
10792
- return align === "none" ? 1 : align === "center" ? 3 : 2;
10793
- }
10794
- return { table(node) {
10795
- const parsed = parseTable(sourceCode, node);
10796
- if (!parsed) return;
10797
- verifyTablePipes(new TableContext(parsed));
10798
- } };
10799
- }
10800
- });
10801
- /**
10802
- * Convert a parsed table row to row data
10803
- */
10804
- function parsedTableRowToRowData(parsedRow) {
10805
- return { cells: parsedRow.cells.map((cell, index) => {
10806
- const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10807
- return {
10808
- type: "cell",
10809
- leadingPipe: cell.leadingPipe,
10810
- content: cell.cell,
10811
- trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10812
- };
10813
- }) };
10814
- }
10815
- /**
10816
- * Convert a parsed table delimiter row to row data
10817
- */
10818
- function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
10819
- return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
10820
- const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10821
- return {
10822
- type: "delimiter",
10823
- leadingPipe: cell.leadingPipe,
10824
- delimiter: cell.delimiter,
10825
- align: cell.delimiter.align,
10826
- trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10827
- };
10828
- }) };
10829
- }
10830
-
10831
- //#endregion
10832
- //#region src/rules/table-pipe-spacing.ts
10833
- /**
10834
- * Parsed options
10835
- */
10836
- function parseOptions(options) {
10837
- const spaceOption = options?.space;
10838
- const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10839
- const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10840
- const cellAlignOption = options?.cellAlign;
10841
- const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10842
- const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10843
- const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10844
- const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10845
- return {
10846
- leadingSpace,
10847
- trailingSpace,
10848
- cellAlignByDelimiter: {
10849
- none: adjustAlign(defaultDelimiterCellAlign),
10850
- left: adjustAlign(leftAlignmentDelimiterCellAlign),
10851
- center: adjustAlign(centerAlignmentDelimiterCellAlign),
10852
- right: adjustAlign(rightAlignmentDelimiterCellAlign)
10853
- }
10854
- };
10855
- /**
10856
- * Adjust the alignment option based on the spacing options.
10857
- */
10858
- function adjustAlign(align) {
10859
- if (align === "left") {
10860
- if (trailingSpace === "always") return "left";
10861
- return "ignore";
10862
- }
10863
- if (align === "center") {
10864
- if (leadingSpace === "always" && trailingSpace === "always") return "center";
10865
- return "ignore";
10866
- }
10867
- if (align === "right") {
10868
- if (leadingSpace === "always") return "right";
10869
- return "ignore";
10870
- }
10871
- return align;
10872
- }
10873
- }
10874
- var table_pipe_spacing_default = createRule("table-pipe-spacing", {
10875
- meta: {
10876
- type: "layout",
10877
- docs: {
10878
- description: "enforce consistent spacing around table pipes",
10879
- categories: ["standard"],
10880
- listCategory: "Whitespace"
10881
- },
10882
- fixable: "whitespace",
10883
- hasSuggestions: false,
10884
- schema: [{
10885
- type: "object",
10886
- properties: {
10887
- space: { anyOf: [{ enum: ["always", "never"] }, {
10888
- type: "object",
10889
- properties: {
10890
- leading: { enum: ["always", "never"] },
10891
- trailing: { enum: ["always", "never"] }
10892
- },
10893
- additionalProperties: false
10894
- }] },
10895
- cellAlign: { anyOf: [{ enum: [
10896
- "left",
10897
- "center",
10898
- "right"
10899
- ] }, {
10900
- type: "object",
10901
- properties: {
10902
- defaultDelimiter: { enum: [
10903
- "left",
10904
- "center",
10905
- "right",
10906
- "ignore"
10907
- ] },
10908
- leftAlignmentDelimiter: { enum: [
10909
- "left",
10910
- "center",
10911
- "right",
10912
- "ignore"
10913
- ] },
10914
- centerAlignmentDelimiter: { enum: [
10915
- "left",
10916
- "center",
10917
- "right",
10918
- "ignore"
10919
- ] },
10920
- rightAlignmentDelimiter: { enum: [
10921
- "left",
10922
- "center",
10923
- "right",
10924
- "ignore"
10925
- ] }
10926
- },
10927
- additionalProperties: false
10928
- }] }
10929
- },
10930
- additionalProperties: false
10931
- }],
10932
- messages: {
10933
- expectedSpaceBefore: "Expected 1 space before \"|\".",
10934
- expectedNoSpaceBefore: "Expected no space before \"|\".",
10935
- expectedSpaceAfter: "Expected 1 space after \"|\".",
10936
- expectedNoSpaceAfter: "Expected no space after \"|\".",
10937
- expectedAlignLeft: "Expected 1 space after \"|\" for left-aligned column.",
10938
- expectedNoSpaceAlignLeft: "Expected no space after \"|\" for left-aligned column.",
10939
- expectedAlignRight: "Expected 1 space before \"|\" for right-aligned column.",
10940
- expectedNoSpaceAlignRight: "Expected no space before \"|\" for right-aligned column.",
10941
- expectedAlignCenter: "Expected the number of spaces before and after the content to be the same or differ by 1 at most for center-aligned column."
10942
- }
10943
- },
10944
- create(context) {
10945
- const sourceCode = context.sourceCode;
10946
- const options = parseOptions(context.options[0]);
10947
- /**
10948
- * Verify for the leading pipe.
10949
- */
10950
- function verifyLeadingPipe(pipe, nextToken) {
10951
- if (options.leadingSpace === "always") {
10952
- if (pipe.range[1] < nextToken.range[0]) return true;
10953
- context.report({
10954
- loc: pipe.loc,
10955
- messageId: "expectedSpaceAfter",
10956
- fix(fixer) {
10957
- return fixer.insertTextAfterRange(pipe.range, " ");
10958
- }
10959
- });
10960
- return false;
10961
- } else if (options.leadingSpace === "never") {
10962
- if (pipe.range[1] === nextToken.range[0]) return true;
10963
- context.report({
10964
- loc: {
10965
- start: pipe.loc.end,
10966
- end: nextToken.loc.start
10967
- },
10968
- messageId: "expectedNoSpaceAfter",
10969
- fix(fixer) {
10970
- return fixer.removeRange([pipe.range[1], nextToken.range[0]]);
10971
- }
10972
- });
10973
- return false;
10835
+ });
10836
+ return false;
10837
+ } else if (options.leadingSpace === "never") {
10838
+ if (pipe.range[1] === nextToken.range[0]) return true;
10839
+ context.report({
10840
+ loc: {
10841
+ start: pipe.loc.end,
10842
+ end: nextToken.loc.start
10843
+ },
10844
+ messageId: "expectedNoSpaceAfter",
10845
+ fix(fixer) {
10846
+ return fixer.removeRange([pipe.range[1], nextToken.range[0]]);
10847
+ }
10848
+ });
10849
+ return false;
10974
10850
  }
10975
10851
  return true;
10976
10852
  }
@@ -11201,6 +11077,256 @@ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
11201
11077
  }
11202
11078
  });
11203
11079
 
11080
+ //#endregion
11081
+ //#region src/rules/table-pipe-alignment.ts
11082
+ var table_pipe_alignment_default = createRule("table-pipe-alignment", {
11083
+ meta: {
11084
+ type: "layout",
11085
+ docs: {
11086
+ description: "enforce consistent alignment of table pipes",
11087
+ categories: ["standard"],
11088
+ listCategory: "Decorative"
11089
+ },
11090
+ fixable: "code",
11091
+ hasSuggestions: false,
11092
+ schema: [{
11093
+ type: "object",
11094
+ properties: { column: { enum: ["minimum", "consistent"] } },
11095
+ additionalProperties: false
11096
+ }],
11097
+ messages: {
11098
+ addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
11099
+ removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
11100
+ }
11101
+ },
11102
+ create(context) {
11103
+ const sourceCode = context.sourceCode;
11104
+ const columnOption = (context.options[0] || {}).column || "minimum";
11105
+ class TableContext {
11106
+ rows;
11107
+ columnCount;
11108
+ _cacheHasSpaceBetweenContentAndTrailingPipe = /* @__PURE__ */ new Map();
11109
+ _cacheExpectedPipePosition = /* @__PURE__ */ new Map();
11110
+ constructor(parsed) {
11111
+ const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
11112
+ for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
11113
+ this.rows = rows;
11114
+ let columnCount = 0;
11115
+ for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
11116
+ this.columnCount = columnCount;
11117
+ }
11118
+ /**
11119
+ * Get the expected pipe position for the index
11120
+ */
11121
+ getExpectedPipePosition(pipeIndex) {
11122
+ let v = this._cacheExpectedPipePosition.get(pipeIndex);
11123
+ if (v !== void 0) return v;
11124
+ v = this._computeExpectedPipePositionWithoutCache(pipeIndex);
11125
+ this._cacheExpectedPipePosition.set(pipeIndex, v);
11126
+ return v;
11127
+ }
11128
+ /**
11129
+ * Check if there is at least one space between content and trailing pipe
11130
+ * for the index
11131
+ *
11132
+ * This is used to determine if the pipe should be aligned with a space before it.
11133
+ */
11134
+ hasSpaceBetweenContentAndTrailingPipe(pipeIndex) {
11135
+ if (pipeIndex === 0) return false;
11136
+ let v = this._cacheHasSpaceBetweenContentAndTrailingPipe.get(pipeIndex);
11137
+ if (v != null) return v;
11138
+ v = this._hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex);
11139
+ this._cacheHasSpaceBetweenContentAndTrailingPipe.set(pipeIndex, v);
11140
+ return v;
11141
+ }
11142
+ /**
11143
+ * Get the expected pipe position for the index
11144
+ */
11145
+ _computeExpectedPipePositionWithoutCache(pipeIndex) {
11146
+ if (pipeIndex === 0) {
11147
+ const firstCell = this.rows[0].cells[0];
11148
+ const firstToken = firstCell.leadingPipe ?? firstCell.content;
11149
+ if (!firstToken) return null;
11150
+ return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
11151
+ }
11152
+ if (columnOption === "minimum") return this.getMinimumPipePosition(pipeIndex);
11153
+ else if (columnOption === "consistent") {
11154
+ const columnIndex = pipeIndex - 1;
11155
+ for (const row of this.rows) {
11156
+ if (row.cells.length <= columnIndex) continue;
11157
+ const cell = row.cells[columnIndex];
11158
+ if (cell.type === "delimiter" || !cell.trailingPipe) continue;
11159
+ const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
11160
+ return Math.max(width, this.getMinimumPipePosition(pipeIndex) || 0);
11161
+ }
11162
+ }
11163
+ return null;
11164
+ }
11165
+ /**
11166
+ * Get the minimum pipe position for the index
11167
+ */
11168
+ getMinimumPipePosition(pipeIndex) {
11169
+ const spacingRuleOptions = getCurrentTablePipeSpacingOption(sourceCode);
11170
+ const needSpaceBeforePipe = spacingRuleOptions ? spacingRuleOptions.trailingSpace === "always" : this.hasSpaceBetweenContentAndTrailingPipe(pipeIndex);
11171
+ let maxWidth = 0;
11172
+ const columnIndex = pipeIndex - 1;
11173
+ for (const row of this.rows) {
11174
+ if (row.cells.length <= columnIndex) continue;
11175
+ const cell = row.cells[columnIndex];
11176
+ let width;
11177
+ if (cell.type === "delimiter") {
11178
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
11179
+ width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
11180
+ } else {
11181
+ if (!cell.content) continue;
11182
+ width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
11183
+ }
11184
+ if (needSpaceBeforePipe) width += 1;
11185
+ maxWidth = Math.max(maxWidth, width);
11186
+ }
11187
+ return maxWidth;
11188
+ }
11189
+ /**
11190
+ * Check if there is at least one space between content and trailing pipe
11191
+ */
11192
+ _hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex) {
11193
+ const columnIndex = pipeIndex - 1;
11194
+ for (const row of this.rows) {
11195
+ if (row.cells.length <= columnIndex) continue;
11196
+ const cell = row.cells[columnIndex];
11197
+ if (!cell.trailingPipe) continue;
11198
+ let content;
11199
+ if (cell.type === "delimiter") content = cell.delimiter;
11200
+ else {
11201
+ if (!cell.content) continue;
11202
+ content = cell.content;
11203
+ }
11204
+ if (content.range[1] < cell.trailingPipe.range[0]) continue;
11205
+ return false;
11206
+ }
11207
+ return true;
11208
+ }
11209
+ }
11210
+ /**
11211
+ * Verify the table pipes
11212
+ */
11213
+ function verifyTablePipes(table) {
11214
+ const targetRows = [...table.rows];
11215
+ for (const row of targetRows) for (let pipeIndex = 0; pipeIndex <= table.columnCount; pipeIndex++) if (!verifyRowPipe(row, pipeIndex, table)) break;
11216
+ }
11217
+ /**
11218
+ * Verify the pipe in the row
11219
+ */
11220
+ function verifyRowPipe(row, pipeIndex, table) {
11221
+ let cellIndex;
11222
+ let pipe;
11223
+ if (pipeIndex === 0) {
11224
+ cellIndex = 0;
11225
+ pipe = "leadingPipe";
11226
+ } else {
11227
+ cellIndex = pipeIndex - 1;
11228
+ pipe = "trailingPipe";
11229
+ }
11230
+ if (row.cells.length <= cellIndex) return true;
11231
+ const cell = row.cells[cellIndex];
11232
+ const pipeToken = cell[pipe];
11233
+ if (!pipeToken) return true;
11234
+ return verifyPipe(pipeToken, pipeIndex, table, cell);
11235
+ }
11236
+ /**
11237
+ * Verify the pipe position
11238
+ */
11239
+ function verifyPipe(pipe, pipeIndex, table, cell) {
11240
+ const expected = table.getExpectedPipePosition(pipeIndex);
11241
+ if (expected == null) return true;
11242
+ const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
11243
+ const diff = expected - actual;
11244
+ if (diff === 0) return true;
11245
+ context.report({
11246
+ loc: pipe.loc,
11247
+ messageId: diff > 0 ? "addSpaces" : "removeSpaces",
11248
+ data: {
11249
+ expected: String(expected),
11250
+ count: String(Math.abs(diff)),
11251
+ plural: Math.abs(diff) === 1 ? "" : "s"
11252
+ },
11253
+ fix(fixer) {
11254
+ if (diff > 0) {
11255
+ if (pipeIndex === 0 || cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
11256
+ return fixer.insertTextAfterRange([cell.delimiter.range[0], cell.delimiter.range[0] + 1], "-".repeat(diff));
11257
+ }
11258
+ const baseEdit = fixRemoveSpaces();
11259
+ if (baseEdit) return baseEdit;
11260
+ if (pipeIndex === 0 || cell.type === "cell") return null;
11261
+ const beforeDelimiter = sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1);
11262
+ const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
11263
+ const newLength = expected - widthBeforeDelimiter;
11264
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
11265
+ const spaceAfter = table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? " " : "";
11266
+ if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
11267
+ const delimiterPrefix = cell.align === "left" || cell.align === "center" ? ":" : "";
11268
+ const delimiterSuffix = (cell.align === "right" || cell.align === "center" ? ":" : "") + spaceAfter;
11269
+ const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
11270
+ return fixer.replaceTextRange([cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
11271
+ /**
11272
+ * Fixer to remove spaces before the pipe
11273
+ */
11274
+ function fixRemoveSpaces() {
11275
+ const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
11276
+ const trimmedBeforePipe = beforePipe.trimEnd();
11277
+ const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
11278
+ const widthBeforePipe = getTextWidth(trimmedBeforePipe);
11279
+ const newSpacesLength = expected - widthBeforePipe;
11280
+ if (newSpacesLength < (table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? 1 : 0)) return null;
11281
+ return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
11282
+ }
11283
+ }
11284
+ });
11285
+ return false;
11286
+ }
11287
+ /**
11288
+ * Get the minimum delimiter length based on alignment
11289
+ */
11290
+ function getMinimumDelimiterLength(align) {
11291
+ return align === "none" ? 1 : align === "center" ? 3 : 2;
11292
+ }
11293
+ return { table(node) {
11294
+ const parsed = parseTable(sourceCode, node);
11295
+ if (!parsed) return;
11296
+ verifyTablePipes(new TableContext(parsed));
11297
+ } };
11298
+ }
11299
+ });
11300
+ /**
11301
+ * Convert a parsed table row to row data
11302
+ */
11303
+ function parsedTableRowToRowData(parsedRow) {
11304
+ return { cells: parsedRow.cells.map((cell, index) => {
11305
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
11306
+ return {
11307
+ type: "cell",
11308
+ leadingPipe: cell.leadingPipe,
11309
+ content: cell.cell,
11310
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
11311
+ };
11312
+ }) };
11313
+ }
11314
+ /**
11315
+ * Convert a parsed table delimiter row to row data
11316
+ */
11317
+ function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
11318
+ return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
11319
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
11320
+ return {
11321
+ type: "delimiter",
11322
+ leadingPipe: cell.leadingPipe,
11323
+ delimiter: cell.delimiter,
11324
+ align: cell.delimiter.align,
11325
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
11326
+ };
11327
+ }) };
11328
+ }
11329
+
11204
11330
  //#endregion
11205
11331
  //#region src/rules/thematic-break-character-style.ts
11206
11332
  var thematic_break_character_style_default = createRule("thematic-break-character-style", {
@@ -11442,6 +11568,7 @@ const rules$1 = [
11442
11568
  ordered_list_marker_sequence_default,
11443
11569
  ordered_list_marker_start_default,
11444
11570
  ordered_list_marker_style_default,
11571
+ padded_custom_containers_default,
11445
11572
  padding_line_between_blocks_default,
11446
11573
  prefer_autolinks_default,
11447
11574
  prefer_fenced_code_blocks_default,
@@ -11539,6 +11666,7 @@ const rules$2 = {
11539
11666
  "markdown-preferences/ordered-list-marker-sequence": "error",
11540
11667
  "markdown-preferences/ordered-list-marker-start": "error",
11541
11668
  "markdown-preferences/ordered-list-marker-style": "error",
11669
+ "markdown-preferences/padded-custom-containers": "error",
11542
11670
  "markdown-preferences/padding-line-between-blocks": "error",
11543
11671
  "markdown-preferences/prefer-autolinks": "error",
11544
11672
  "markdown-preferences/prefer-fenced-code-blocks": "error",
@@ -11560,7 +11688,7 @@ var meta_exports = /* @__PURE__ */ __export({
11560
11688
  version: () => version
11561
11689
  });
11562
11690
  const name = "eslint-plugin-markdown-preferences";
11563
- const version = "0.28.0";
11691
+ const version = "0.29.1";
11564
11692
 
11565
11693
  //#endregion
11566
11694
  //#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.28.0",
3
+ "version": "0.29.1",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,7 +25,7 @@
25
25
  "markdownlint": "npx -y markdownlint-cli2 .",
26
26
  "test": "npm run mocha -- \"tests/src/**/*.ts\" --reporter=dot --timeout=60000",
27
27
  "test:debug": "node --experimental-strip-types --experimental-transform-types ./node_modules/mocha/bin/mocha.js \"tests/src/**/*.ts\" --reporter=dot --timeout=60000",
28
- "cover": "c8 --reporter=lcov npm run test",
28
+ "cover": "c8 --reporter=lcov --reporter=text npm run test:debug",
29
29
  "test:update": "npm run mocha -- \"tests/src/**/*.ts\" --reporter=dot --update",
30
30
  "update": "npm run ts -- ./tools/update.ts && npm run eslint-fix",
31
31
  "update:resources": "npm run ts -- ./tools/update-resources.ts && npm run eslint-fix",