eslint-plugin-markdown-preferences 0.27.0 → 0.29.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
@@ -164,24 +164,25 @@ The rules with the following 💄 are included in the `standard` config.
164
164
 
165
165
  <!-- prettier-ignore-start -->
166
166
 
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-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. | | ⭐💄 |
179
- | [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 | 🔧 | 💄 |
180
- | [markdown-preferences/prefer-autolinks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html) | enforce the use of autolinks for URLs | 🔧 | ⭐💄 |
181
- | [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 | 🔧 | ⭐💄 |
182
- | [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 | 🔧 | |
183
- | [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 | 🔧 | 💄 |
184
- | [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. | 🔧 | 💄 |
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 containers | 🔧 | ⭐💄 |
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. | 🔧 | 💄 |
185
186
 
186
187
  <!-- prettier-ignore-end -->
187
188
 
@@ -203,6 +204,7 @@ The rules with the following 💄 are included in the `standard` config.
203
204
  | [markdown-preferences/no-multi-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multi-spaces.html) | disallow multiple spaces | 🔧 | 💄 |
204
205
  | [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. | 🔧 | 💄 |
205
206
  | [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
+ | [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 | 🔧 | 💄 |
206
208
  | [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 | 🔧 | 💄 |
207
209
  | [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 | 🔧 | 💄 |
208
210
 
package/lib/index.d.ts CHANGED
@@ -125,6 +125,11 @@ interface RuleOptions {
125
125
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html
126
126
  */
127
127
  'markdown-preferences/list-marker-alignment'?: Linter.RuleEntry<MarkdownPreferencesListMarkerAlignment>;
128
+ /**
129
+ * disallow implicit block closing for fenced code blocks, math blocks, and custom containers
130
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-implicit-block-closing.html
131
+ */
132
+ 'markdown-preferences/no-implicit-block-closing'?: Linter.RuleEntry<[]>;
128
133
  /**
129
134
  * disallow laziness in blockquotes
130
135
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html
@@ -165,6 +170,11 @@ interface RuleOptions {
165
170
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-style.html
166
171
  */
167
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>;
168
178
  /**
169
179
  * require or disallow padding lines between blocks
170
180
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html
@@ -384,6 +394,9 @@ type MarkdownPreferencesOrderedListMarkerStyle = [] | [{
384
394
  prefer?: ("n." | "n)");
385
395
  }[];
386
396
  }];
397
+ type MarkdownPreferencesPaddedCustomContainers = [] | [{
398
+ padding?: ("always" | "never");
399
+ }];
387
400
  type MarkdownPreferencesPaddingLineBetweenBlocks = {
388
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" | "*"))[]] | {
389
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" | "*"))[]]);
@@ -503,7 +516,7 @@ declare namespace meta_d_exports {
503
516
  export { name, version };
504
517
  }
505
518
  declare const name: "eslint-plugin-markdown-preferences";
506
- declare const version: "0.27.0";
519
+ declare const version: "0.29.0";
507
520
  //#endregion
508
521
  //#region src/language/ast-types.d.ts
509
522
  type Node = mdast.Node;
@@ -600,7 +613,7 @@ interface RootContentMap {
600
613
  importCodeSnippet: ImportCodeSnippet;
601
614
  }
602
615
  //#endregion
603
- //#region src/language/extended-markdown-ianguage.d.ts
616
+ //#region src/language/extended-markdown-language.d.ts
604
617
  type ExtendedMarkdownSourceCode = TextSourceCodeBase<{
605
618
  LangOptions: MarkdownLanguageOptions;
606
619
  RootNode: Root;
package/lib/index.js CHANGED
@@ -989,28 +989,14 @@ const RE_LANGUAGE = /^(\w*)/u;
989
989
  * Parse the fenced code block.
990
990
  */
991
991
  function parseFencedCodeBlock(sourceCode, node) {
992
+ if (getCodeBlockKind(sourceCode, node) === "indented") return null;
992
993
  const loc = sourceCode.getLoc(node);
993
994
  const range = sourceCode.getRange(node);
994
995
  const text = sourceCode.text.slice(...range);
995
996
  const match = RE_OPENING_FENCE.exec(text);
996
997
  if (!match) return null;
997
998
  const [, fenceText] = match;
998
- const fenceChar = fenceText[0];
999
- let closingFenceText = "";
1000
- const trimmed = text.trimEnd();
1001
- const trailingSpacesLength = text.length - trimmed.length;
1002
- for (let index = trimmed.length - 1; index >= 0; index--) {
1003
- const c = trimmed[index];
1004
- if (c === fenceChar || isSpaceOrTab(c)) {
1005
- closingFenceText = c + closingFenceText;
1006
- continue;
1007
- }
1008
- if (c === "\n") break;
1009
- return null;
1010
- }
1011
- closingFenceText = closingFenceText.trimStart();
1012
- if (!closingFenceText || !closingFenceText.startsWith(fenceText)) return null;
1013
- const afterOpeningFence = getParsedLines(sourceCode).get(loc.start.line).text.slice(fenceText.length);
999
+ const afterOpeningFence = sourceCode.lines[loc.start.line - 1].slice(loc.start.column - 1 + fenceText.length);
1014
1000
  const trimmedAfterOpeningFence = afterOpeningFence.trimStart();
1015
1001
  const spaceAfterOpeningFenceLength = afterOpeningFence.length - trimmedAfterOpeningFence.length;
1016
1002
  let languageText = "";
@@ -1019,45 +1005,49 @@ function parseFencedCodeBlock(sourceCode, node) {
1019
1005
  const trimmedAfterLanguage = afterLanguage.trimStart();
1020
1006
  const spaceAfterLanguageLength = afterLanguage.length - trimmedAfterLanguage.length;
1021
1007
  const metaText = trimmedAfterLanguage.trimEnd();
1008
+ const openingFenceRange = [range[0], range[0] + fenceText.length];
1022
1009
  const openingFence = {
1023
1010
  text: fenceText,
1024
- range: [range[0], range[0] + fenceText.length],
1025
- loc: {
1026
- start: loc.start,
1027
- end: {
1028
- line: loc.start.line,
1029
- column: loc.start.column + fenceText.length
1030
- }
1031
- }
1011
+ range: openingFenceRange,
1012
+ loc: getSourceLocationFromRange(sourceCode, node, openingFenceRange)
1032
1013
  };
1033
- const language$2 = languageText ? {
1014
+ const languageRange = languageText ? [openingFence.range[1] + spaceAfterOpeningFenceLength, openingFence.range[1] + spaceAfterOpeningFenceLength + languageText.length] : null;
1015
+ const language$2 = languageText && languageRange ? {
1034
1016
  text: languageText,
1035
- range: [openingFence.range[1] + spaceAfterOpeningFenceLength, openingFence.range[1] + spaceAfterOpeningFenceLength + languageText.length],
1036
- loc: {
1037
- start: {
1038
- line: openingFence.loc.end.line,
1039
- column: openingFence.loc.end.column + spaceAfterOpeningFenceLength
1040
- },
1041
- end: {
1042
- line: openingFence.loc.end.line,
1043
- column: openingFence.loc.end.column + spaceAfterOpeningFenceLength + languageText.length
1044
- }
1045
- }
1017
+ range: languageRange,
1018
+ loc: getSourceLocationFromRange(sourceCode, node, languageRange)
1046
1019
  } : null;
1047
- const meta = language$2 && metaText ? {
1020
+ const metaRange = language$2 && metaText ? [language$2.range[1] + spaceAfterLanguageLength, language$2.range[1] + spaceAfterLanguageLength + metaText.length] : null;
1021
+ const meta = language$2 && metaText && metaRange ? {
1048
1022
  text: metaText,
1049
- range: [language$2.range[1] + spaceAfterLanguageLength, language$2.range[1] + spaceAfterLanguageLength + metaText.length],
1050
- loc: {
1051
- start: {
1052
- line: language$2.loc.end.line,
1053
- column: language$2.loc.end.column + spaceAfterLanguageLength
1054
- },
1055
- end: {
1056
- line: language$2.loc.end.line,
1057
- column: language$2.loc.end.column + spaceAfterLanguageLength + metaText.length
1058
- }
1059
- }
1023
+ range: metaRange,
1024
+ loc: getSourceLocationFromRange(sourceCode, node, metaRange)
1060
1025
  } : null;
1026
+ const fenceChar = fenceText[0];
1027
+ let closingFenceText = "";
1028
+ const trimmed = text.trimEnd();
1029
+ const trailingSpacesLength = text.length - trimmed.length;
1030
+ for (let index = trimmed.length - 1; index >= 0; index--) {
1031
+ const c = trimmed[index];
1032
+ if (c === fenceChar || isSpaceOrTab(c)) {
1033
+ closingFenceText = c + closingFenceText;
1034
+ continue;
1035
+ }
1036
+ if (c === ">") {
1037
+ closingFenceText = ` ${closingFenceText}`;
1038
+ continue;
1039
+ }
1040
+ if (c === "\n") break;
1041
+ closingFenceText = "";
1042
+ break;
1043
+ }
1044
+ closingFenceText = closingFenceText.trimStart();
1045
+ if (!closingFenceText || !closingFenceText.startsWith(fenceText)) return {
1046
+ openingFence,
1047
+ language: language$2,
1048
+ meta,
1049
+ closingFence: null
1050
+ };
1061
1051
  return {
1062
1052
  openingFence,
1063
1053
  language: language$2,
@@ -1227,8 +1217,9 @@ var code_fence_length_default = createRule("code-fence-length", {
1227
1217
  actual: parsed.openingFence.text
1228
1218
  },
1229
1219
  messageId: "notPreferred",
1230
- fix(fixer) {
1231
- return [fixer.replaceTextRange(parsed.openingFence.range, expectedFence), fixer.replaceTextRange(parsed.closingFence.range, expectedFence)];
1220
+ *fix(fixer) {
1221
+ yield fixer.replaceTextRange(parsed.openingFence.range, expectedFence);
1222
+ if (parsed.closingFence) yield fixer.replaceTextRange(parsed.closingFence.range, expectedFence);
1232
1223
  }
1233
1224
  });
1234
1225
  }
@@ -1267,14 +1258,14 @@ var code_fence_length_default = createRule("code-fence-length", {
1267
1258
  /**
1268
1259
  * Verify that the opening and closing fence lengths match.
1269
1260
  */
1270
- function verifyClosingFenceLength(node, parsed) {
1271
- if (parsed.openingFence.text.length === parsed.closingFence.text.length) return true;
1261
+ function verifyClosingFenceLength(node, { openingFence, closingFence }) {
1262
+ if (!closingFence || openingFence.text.length === closingFence.text.length) return true;
1272
1263
  context.report({
1273
1264
  node,
1274
- loc: parsed.closingFence.loc,
1265
+ loc: closingFence.loc,
1275
1266
  messageId: "notMatch",
1276
1267
  fix(fixer) {
1277
- return [fixer.replaceTextRange(parsed.closingFence.range, parsed.openingFence.text)];
1268
+ return [fixer.replaceTextRange(closingFence.range, openingFence.text)];
1278
1269
  }
1279
1270
  });
1280
1271
  return false;
@@ -1327,8 +1318,9 @@ var code_fence_style_default = createRule("code-fence-style", {
1327
1318
  actual: parsed.openingFence.text
1328
1319
  },
1329
1320
  messageId: "useCodeFenceStyle",
1330
- fix(fixer) {
1331
- return [fixer.replaceTextRange(parsed.openingFence.range, expectedOpeningFence), fixer.replaceTextRange(parsed.closingFence.range, expectedChar.repeat(Math.max(3, parsed.closingFence.text.length)))];
1321
+ *fix(fixer) {
1322
+ yield fixer.replaceTextRange(parsed.openingFence.range, expectedOpeningFence);
1323
+ if (parsed.closingFence) yield fixer.replaceTextRange(parsed.closingFence.range, expectedChar.repeat(Math.max(3, parsed.closingFence.text.length)));
1332
1324
  }
1333
1325
  });
1334
1326
  } };
@@ -5323,7 +5315,19 @@ function parseMathBlock(sourceCode, node) {
5323
5315
  loc: getSourceLocationFromRange(sourceCode, node, openingSequenceRange)
5324
5316
  };
5325
5317
  const parsedClosing = parseMathClosingSequenceFromText(text);
5326
- if (parsedClosing == null) return null;
5318
+ if (parsedClosing == null || parsedClosing.closingSequence !== parsedOpening.openingSequence) {
5319
+ const contentRange$1 = [openingSequence.range[1] + parsedOpening.after.length, range[1]];
5320
+ return {
5321
+ openingSequence,
5322
+ content: {
5323
+ text: text.slice(parsedOpening.after.length),
5324
+ range: contentRange$1,
5325
+ loc: getSourceLocationFromRange(sourceCode, node, contentRange$1)
5326
+ },
5327
+ closingSequence: null,
5328
+ after: null
5329
+ };
5330
+ }
5327
5331
  const spaceAfterClosingRange = [range[1] - parsedClosing.after.length, range[1]];
5328
5332
  const spaceAfterClosing = {
5329
5333
  text: parsedClosing.after,
@@ -5891,7 +5895,7 @@ var indent_default = createRule("indent", {
5891
5895
  const parsed = parseMathBlock(sourceCode, node);
5892
5896
  if (!parsed) return;
5893
5897
  const loc = sourceCode.getLoc(node);
5894
- const endLineToBeChecked = inlineToBeChecked(parsed.closingSequence.loc.start);
5898
+ const endLineToBeChecked = parsed.closingSequence && inlineToBeChecked(parsed.closingSequence.loc.start);
5895
5899
  verifyLinesIndent(endLineToBeChecked ? [loc.start.line, loc.end.line] : [loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5896
5900
  lineNumber,
5897
5901
  block: true
@@ -7541,6 +7545,164 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
7541
7545
  }
7542
7546
  });
7543
7547
 
7548
+ //#endregion
7549
+ //#region src/utils/custom-container.ts
7550
+ const RE_OPENING_SEQUENCE = /^(:{3,})/u;
7551
+ /**
7552
+ * Parse the custom container.
7553
+ */
7554
+ function parseCustomContainer(sourceCode, node) {
7555
+ const loc = sourceCode.getLoc(node);
7556
+ const range = sourceCode.getRange(node);
7557
+ const text = sourceCode.text.slice(...range);
7558
+ const match = RE_OPENING_SEQUENCE.exec(text);
7559
+ if (!match) return null;
7560
+ const [, sequenceText] = match;
7561
+ const afterOpeningSequence = sourceCode.lines[loc.start.line - 1].slice(loc.start.column - 1 + sequenceText.length);
7562
+ const trimmedAfterOpeningSequence = afterOpeningSequence.trimStart();
7563
+ const spaceAfterOpeningSequenceLength = afterOpeningSequence.length - trimmedAfterOpeningSequence.length;
7564
+ const infoText = trimmedAfterOpeningSequence.trimEnd();
7565
+ if (!infoText) return null;
7566
+ const openingSequenceRange = [range[0], range[0] + sequenceText.length];
7567
+ const openingSequence = {
7568
+ text: sequenceText,
7569
+ range: openingSequenceRange,
7570
+ loc: getSourceLocationFromRange(sourceCode, node, openingSequenceRange)
7571
+ };
7572
+ const infoRange = [openingSequence.range[1] + spaceAfterOpeningSequenceLength, openingSequence.range[1] + spaceAfterOpeningSequenceLength + infoText.length];
7573
+ const info = {
7574
+ text: infoText,
7575
+ range: infoRange,
7576
+ loc: getSourceLocationFromRange(sourceCode, node, infoRange)
7577
+ };
7578
+ const sequenceChar = sequenceText[0];
7579
+ let closingSequenceText = "";
7580
+ const trimmed = text.trimEnd();
7581
+ const trailingSpacesLength = text.length - trimmed.length;
7582
+ for (let index = trimmed.length - 1; index >= 0; index--) {
7583
+ const c = trimmed[index];
7584
+ if (c === sequenceChar || isSpaceOrTab(c)) {
7585
+ closingSequenceText = c + closingSequenceText;
7586
+ continue;
7587
+ }
7588
+ if (c === ">") {
7589
+ closingSequenceText = ` ${closingSequenceText}`;
7590
+ continue;
7591
+ }
7592
+ if (c === "\n") break;
7593
+ closingSequenceText = "";
7594
+ break;
7595
+ }
7596
+ closingSequenceText = closingSequenceText.trimStart();
7597
+ if (!closingSequenceText || !closingSequenceText.startsWith(sequenceText)) return {
7598
+ openingSequence,
7599
+ info,
7600
+ closingSequence: null
7601
+ };
7602
+ const closingSequenceRange = [range[1] - trailingSpacesLength - closingSequenceText.length, range[1] - trailingSpacesLength];
7603
+ return {
7604
+ openingSequence,
7605
+ info,
7606
+ closingSequence: {
7607
+ text: closingSequenceText,
7608
+ range: closingSequenceRange,
7609
+ loc: getSourceLocationFromRange(sourceCode, node, closingSequenceRange)
7610
+ }
7611
+ };
7612
+ }
7613
+
7614
+ //#endregion
7615
+ //#region src/rules/no-implicit-block-closing.ts
7616
+ var no_implicit_block_closing_default = createRule("no-implicit-block-closing", {
7617
+ meta: {
7618
+ type: "suggestion",
7619
+ docs: {
7620
+ description: "disallow implicit block closing for fenced code blocks, math blocks, and custom containers",
7621
+ categories: ["recommended", "standard"],
7622
+ listCategory: "Notation"
7623
+ },
7624
+ fixable: "code",
7625
+ hasSuggestions: false,
7626
+ schema: [],
7627
+ messages: {
7628
+ missingClosingFence: "Missing closing fence for fenced code block.",
7629
+ missingClosingMath: "Missing closing sequence for math block.",
7630
+ missingClosingContainer: "Missing closing sequence for custom container."
7631
+ }
7632
+ },
7633
+ create(context) {
7634
+ const sourceCode = context.sourceCode;
7635
+ return {
7636
+ code(node) {
7637
+ const parsed = parseFencedCodeBlock(sourceCode, node);
7638
+ if (!parsed) return;
7639
+ if (parsed.closingFence) return;
7640
+ context.report({
7641
+ node,
7642
+ loc: parsed.openingFence.loc,
7643
+ messageId: "missingClosingFence",
7644
+ fix(fixer) {
7645
+ const openingLoc = parsed.openingFence.loc;
7646
+ const prefix = sourceCode.lines[openingLoc.start.line - 1].slice(0, openingLoc.start.column - 1).replace(/[^\s>]/gu, " ");
7647
+ const closingFence = parsed.openingFence.text;
7648
+ return fixer.insertTextAfter(node, `\n${prefix}${closingFence}`);
7649
+ }
7650
+ });
7651
+ },
7652
+ math(node) {
7653
+ const parsed = parseMathBlock(sourceCode, node);
7654
+ if (!parsed) return;
7655
+ if (parsed.closingSequence) return;
7656
+ context.report({
7657
+ node,
7658
+ loc: parsed.openingSequence.loc,
7659
+ messageId: "missingClosingMath",
7660
+ fix(fixer) {
7661
+ const openingLoc = parsed.openingSequence.loc;
7662
+ const prefix = sourceCode.lines[openingLoc.start.line - 1].slice(0, openingLoc.start.column - 1).replace(/[^\s>]/gu, " ");
7663
+ const closingSequence = parsed.openingSequence.text;
7664
+ return fixer.insertTextAfter(node, `\n${prefix}${closingSequence}`);
7665
+ }
7666
+ });
7667
+ },
7668
+ customContainer(node) {
7669
+ const parsed = parseCustomContainer(sourceCode, node);
7670
+ if (!parsed) return;
7671
+ if (parsed.closingSequence) return;
7672
+ context.report({
7673
+ node,
7674
+ loc: parsed.openingSequence.loc,
7675
+ messageId: "missingClosingContainer",
7676
+ fix(fixer) {
7677
+ if (withinSameLengthOpeningCustomContainer(sourceCode, node, parsed.openingSequence.text.length)) return null;
7678
+ const openingLoc = parsed.openingSequence.loc;
7679
+ const prefix = sourceCode.lines[openingLoc.start.line - 1].slice(0, openingLoc.start.column - 1).replace(/[^\s>]/gu, " ");
7680
+ const closingSequence = parsed.openingSequence.text;
7681
+ return fixer.insertTextAfter(node, `\n${prefix}${closingSequence}`);
7682
+ }
7683
+ });
7684
+ }
7685
+ };
7686
+ }
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
+ }
7705
+
7544
7706
  //#endregion
7545
7707
  //#region src/rules/no-laziness-blockquotes.ts
7546
7708
  var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
@@ -7854,31 +8016,37 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7854
8016
  const sourceCode = context.sourceCode;
7855
8017
  let codeText = sourceCode.text;
7856
8018
  return {
8019
+ customContainer: verifyCustomContainer,
8020
+ code: verifyCodeBlock,
7857
8021
  definition: verifyLinkDefinition,
7858
8022
  footnoteDefinition: verifyFootnoteDefinition,
7859
8023
  heading: verifyHeading,
7860
8024
  image: verifyImage,
7861
8025
  imageReference: verifyImageReference,
8026
+ inlineMath: verifyInlineMath,
7862
8027
  link: verifyLink,
7863
8028
  linkReference: verifyLinkReference,
7864
8029
  listItem: verifyListItem,
7865
- blockquote: processBlockquote,
8030
+ table: verifyTable,
7866
8031
  text: verifyText,
7867
- table: verifyTable
8032
+ math: verifyMathBlock,
8033
+ blockquote: processBlockquote
7868
8034
  };
7869
8035
  /**
7870
- * Verify a text node.
8036
+ * Verify a code block node.
7871
8037
  */
7872
- function verifyText(node) {
7873
- verifyTextInNode(node);
8038
+ function verifyCustomContainer(node) {
8039
+ const parsed = parseCustomContainer(sourceCode, node);
8040
+ if (!parsed) return;
8041
+ verifyTextInRange(node, [parsed.openingSequence.range[0], parsed.info.range[1]]);
7874
8042
  }
7875
8043
  /**
7876
- * Verify a table node.
8044
+ * Verify a code block node.
7877
8045
  */
7878
- function verifyTable(node) {
7879
- const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
7880
- if (!parsedDelimiterRow) return;
7881
- verifyTextInRange(node, parsedDelimiterRow.range);
8046
+ function verifyCodeBlock(node) {
8047
+ const parsed = parseFencedCodeBlock(sourceCode, node);
8048
+ if (!parsed) return;
8049
+ verifyTextInRange(node, [parsed.openingFence.range[0], (parsed.meta ?? parsed.language ?? parsed.openingFence).range[1]]);
7882
8050
  }
7883
8051
  /**
7884
8052
  * Verify a definition node.
@@ -7919,6 +8087,12 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7919
8087
  verifyTextInNode(node);
7920
8088
  }
7921
8089
  /**
8090
+ * Verify an inline math node
8091
+ */
8092
+ function verifyInlineMath(node) {
8093
+ verifyTextInNode(node);
8094
+ }
8095
+ /**
7922
8096
  * Verify a link node
7923
8097
  */
7924
8098
  function verifyLink(node) {
@@ -7954,6 +8128,26 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7954
8128
  codeText = newCodeText;
7955
8129
  }
7956
8130
  /**
8131
+ * Verify a table node.
8132
+ */
8133
+ function verifyTable(node) {
8134
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
8135
+ if (!parsedDelimiterRow) return;
8136
+ verifyTextInRange(node, parsedDelimiterRow.range);
8137
+ }
8138
+ /**
8139
+ * Verify a text node.
8140
+ */
8141
+ function verifyText(node) {
8142
+ verifyTextInNode(node);
8143
+ }
8144
+ /**
8145
+ * Verify a math block node
8146
+ */
8147
+ function verifyMathBlock(node) {
8148
+ verifyTextInNode(node);
8149
+ }
8150
+ /**
7957
8151
  * Verify spaces in a node
7958
8152
  */
7959
8153
  function verifyTextInNode(node) {
@@ -8749,6 +8943,101 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
8749
8943
  }
8750
8944
  });
8751
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
+
8752
9041
  //#endregion
8753
9042
  //#region src/rules/padding-line-between-blocks.ts
8754
9043
  /**
@@ -11260,6 +11549,7 @@ const rules$1 = [
11260
11549
  link_paren_spacing_default,
11261
11550
  link_title_style_default,
11262
11551
  list_marker_alignment_default,
11552
+ no_implicit_block_closing_default,
11263
11553
  no_laziness_blockquotes_default,
11264
11554
  no_multi_spaces_default,
11265
11555
  no_multiple_empty_lines_default,
@@ -11268,6 +11558,7 @@ const rules$1 = [
11268
11558
  ordered_list_marker_sequence_default,
11269
11559
  ordered_list_marker_start_default,
11270
11560
  ordered_list_marker_style_default,
11561
+ padded_custom_containers_default,
11271
11562
  padding_line_between_blocks_default,
11272
11563
  prefer_autolinks_default,
11273
11564
  prefer_fenced_code_blocks_default,
@@ -11310,6 +11601,7 @@ const rules$3 = {
11310
11601
  "markdown-preferences/blockquote-marker-alignment": "error",
11311
11602
  "markdown-preferences/hard-linebreak-style": "error",
11312
11603
  "markdown-preferences/list-marker-alignment": "error",
11604
+ "markdown-preferences/no-implicit-block-closing": "error",
11313
11605
  "markdown-preferences/no-laziness-blockquotes": "error",
11314
11606
  "markdown-preferences/no-text-backslash-linebreak": "error",
11315
11607
  "markdown-preferences/prefer-autolinks": "error",
@@ -11355,6 +11647,7 @@ const rules$2 = {
11355
11647
  "markdown-preferences/link-paren-spacing": "error",
11356
11648
  "markdown-preferences/link-title-style": "error",
11357
11649
  "markdown-preferences/list-marker-alignment": "error",
11650
+ "markdown-preferences/no-implicit-block-closing": "error",
11358
11651
  "markdown-preferences/no-laziness-blockquotes": "error",
11359
11652
  "markdown-preferences/no-multi-spaces": "error",
11360
11653
  "markdown-preferences/no-multiple-empty-lines": "error",
@@ -11363,6 +11656,7 @@ const rules$2 = {
11363
11656
  "markdown-preferences/ordered-list-marker-sequence": "error",
11364
11657
  "markdown-preferences/ordered-list-marker-start": "error",
11365
11658
  "markdown-preferences/ordered-list-marker-style": "error",
11659
+ "markdown-preferences/padded-custom-containers": "error",
11366
11660
  "markdown-preferences/padding-line-between-blocks": "error",
11367
11661
  "markdown-preferences/prefer-autolinks": "error",
11368
11662
  "markdown-preferences/prefer-fenced-code-blocks": "error",
@@ -11384,7 +11678,7 @@ var meta_exports = /* @__PURE__ */ __export({
11384
11678
  version: () => version
11385
11679
  });
11386
11680
  const name = "eslint-plugin-markdown-preferences";
11387
- const version = "0.27.0";
11681
+ const version = "0.29.0";
11388
11682
 
11389
11683
  //#endregion
11390
11684
  //#region src/language/extensions/micromark-custom-container.ts
@@ -11752,7 +12046,7 @@ function parseExtendedMarkdown(code) {
11752
12046
  }
11753
12047
 
11754
12048
  //#endregion
11755
- //#region src/language/extended-markdown-ianguage.ts
12049
+ //#region src/language/extended-markdown-language.ts
11756
12050
  var ExtendedMarkdownLanguage = class {
11757
12051
  /**
11758
12052
  * The type of file to read.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {