Repository: platers/obsidian-linter (ts)

Issue:
Bug: Empty Line Around Code Fences breaks blockquotes/callouts
- [X] I have verified that I am on the latest version of the Linter

## Describe the Bug

The option "Empty Line Around Code Fences" inside a callout/blockquote adds the blank line without a ">" which breaks the callout, separating the block.

What are your thoughts on this? Can logic be added to check if the paragraph is in a callout/blockquote and add a blank line with a ">"?

FYI, I had this issue after a certain release, around v1.0 or so.

## How to Reproduce

`````` markdown
> [!abstract]- Mentions
> ```dataview
> XXX
> ```
``````
Becomes:

`````` markdown
> [!abstract]- Mentions

> ```dataview
> XXX
> ```
``````

## Device

- [X] Desktop
- [X] Mobile

## Trace

Running linter
Running Format Tags in YAML
format-tags-in-yaml: 0.19999998807907104 ms
Running Escape YAML Special Characters
escape-yaml-special-characters: 0.09999996423721313 ms
rules before regular rules: 0.2999999523162842 ms
Running Format YAML Array
format-yaml-array: 0.40000003576278687 ms
Running Move Tags to YAML
move-tags-to-yaml: 1.199999988079071 ms
Running File Name Heading
file-name-heading: 1.300000011920929 ms
Running Header Increment
header-increment: 0.699999988079071 ms
Running Headings Start Line
headings-start-line: 0.3999999761581421 ms
Running Remove Trailing Punctuation in Heading
remove-trailing-punctuation-in-heading: 0.699999988079071 ms
Running Footnote after Punctuation
footnote-after-punctuation: 1 ms
Running Move Footnotes to the bottom
move-footnotes-to-the-bottom: 0.699999988079071 ms
Running Convert Bullet List Markers
convert-bullet-list-markers: 0.9000000357627869 ms
Running Emphasis Style
emphasis-style: 0.8999999761581421 ms
Running Proper Ellipsis
proper-ellipsis: 0.800000011920929 ms
Running Remove Consecutive List Markers
remove-consecutive-list-markers: 0.6000000238418579 ms
Running Remove Empty List Markers
remove-empty-list-markers: 0.5999999642372131 ms
Running Remove Hyphenated Line Breaks
remove-hyphenated-line-breaks: 0.699999988079071 ms
Running Remove Multiple Spaces
remove-multiple-spaces: 0.9000000357627869 ms
Running Strong Style
strong-style: 0.8999999761581421 ms
Running Two Spaces Between Lines with Content
two-spaces-between-lines-with-content: 0.19999998807907104 ms
Running Compact YAML
compact-yaml: 0.10000002384185791 ms
Running Consecutive blank lines
consecutive-blank-lines: 0.5 ms
Running Empty Line Around Blockquotes
empty-line-around-blockquotes: 0.3999999761581421 ms
Running Empty Line Around Code Fences
empty-line-around-code-fences: 0.19999998807907104 ms
Running Empty Line Around Math Blocks
empty-line-around-math-blocks: 0.6000000238418579 ms
Running Empty Line Around Tables
empty-line-around-tables: 0.5999999642372131 ms
Running Heading blank lines
heading-blank-lines: 0.800000011920929 ms
Running Line Break at Document End
line-break-at-document-end: 0 ms
Running Paragraph blank lines
paragraph-blank-lines: 0.5999999642372131 ms
Running Remove Empty Lines Between List Markers and Checklists
remove-empty-lines-between-list-markers-and-checklists: 0.8999999761581421 ms
Running Remove link spacing
remove-link-spacing: 0.10000002384185791 ms
Running Space after list markers
space-after-list-markers: 0.5 ms
Running Trailing spaces
trailing-spaces: 0.6000000238418579 ms
Running Custom Regex
custom regex rules: 0.19999998807907104 ms
Running Capitalize Headings
capitalize-headings: 0.5 ms
Running YAML Key Sort
yaml-key-sort: 0.699999988079071 ms
rules after regular rules: 1.5 ms
rules running: 21.19999998807907 ms
Running Custom Lint Commands

Pull request:
Fixed an Issue with `Empty Lines Around Code` Breaking Some Callouts Apart
Fixes #833 

Changes Made:
- Added tests for the issue in question
- Updated the logic to only use the special callout logic to prevent merging callout content when dealing with a empty lines around blockquotes to prevent accidentally breaking some callouts apart

Patch:
diff --git a/src/utils/mdast.ts b/src/utils/mdast.ts
index 9fa400d..bbe26c6 100644
--- a/src/utils/mdast.ts
+++ b/src/utils/mdast.ts
@@ -598,7 +598,7 @@ export function ensureEmptyLinesAroundBlockquotes(text: string): string {
       endIndex++;
     }
 
-    text = makeSureContentHasEmptyLinesAddedBeforeAndAfter(text, position.start.offset, endIndex);
+    text = makeSureContentHasEmptyLinesAddedBeforeAndAfter(text, position.start.offset, endIndex, true);
   }
 
   return text;
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
index bdcb228..a398aa4 100644
--- a/src/utils/strings.ts
+++ b/src/utils/strings.ts
@@ -95,7 +95,7 @@ function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFile(text: str
   return text.substring(0, startOfNewContent) + '\n' + text.substring(startOfContent);
 }
 
-function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(text: string, startOfLine: string, startOfContent: number, isCallout: boolean = false): string {
+function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(text: string, startOfLine: string, startOfContent: number, isCallout: boolean = false, addingEmptyLinesAroundBlockquotes: boolean = false): string {
   if (startOfContent === 0) {
     return text;
   }
@@ -151,7 +151,9 @@ function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockqu
     priorLine = text.substring(indexOfLastNewLine, startOfNewContent);
   }
 
-  return text.substring(0, startOfNewContent) + getEmptyLineForBlockqute(priorLine, isCallout, nestingLevel) + text.substring(startOfContent);
+  const emptyLine = addingEmptyLinesAroundBlockquotes ? getEmptyLineForBlockqute(priorLine, isCallout, nestingLevel) : getEmptyLine(priorLine);
+
+  return text.substring(0, startOfNewContent) + emptyLine + text.substring(startOfContent);
 }
 
 function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFile(text: string, endOfContent: number): string {
@@ -183,7 +185,7 @@ function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFile(text: string
   return text.substring(0, endOfContent) + '\n' + text.substring(endOfNewContent);
 }
 
-function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text: string, startOfLine: string, endOfContent: number, isCallout: boolean = false): string {
+function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text: string, startOfLine: string, endOfContent: number, isCallout: boolean = false, addingEmptyLinesAroundBlockquotes: boolean = false): string {
   if (endOfContent === (text.length - 1)) {
     return text;
   }
@@ -245,7 +247,9 @@ function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote
     nextLine = text.substring(endOfNewContent + 1, indexOfSecondNewLineAfterContent);
   }
 
-  return text.substring(0, endOfContent) + getEmptyLineForBlockqute(nextLine, isCallout, nestingLevel) + text.substring(endOfNewContent);
+  const emptyLine = addingEmptyLinesAroundBlockquotes ? getEmptyLineForBlockqute(nextLine, isCallout, nestingLevel) : getEmptyLine(nextLine);
+
+  return text.substring(0, endOfContent) + emptyLine + text.substring(endOfNewContent);
 }
 
 /**
@@ -253,15 +257,16 @@ function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote
  * @param {string} text - The entire file's contents
  * @param {number} start - The starting index of the content to escape
  * @param {number} end - The ending index of the content to escape
+ * @param {boolean} addingEmptyLinesAroundBlockquotes - Whether or not the logic is meant to add empty lines around blockquotes. This is something meant to better help with spacing around blockquotes.
  * @return {string} The new file contents after the empty lines have been added
  */
-export function makeSureContentHasEmptyLinesAddedBeforeAndAfter(text: string, start: number, end: number): string {
+export function makeSureContentHasEmptyLinesAddedBeforeAndAfter(text: string, start: number, end: number, addingEmptyLinesAroundBlockquotes: boolean = false): string {
   const [startOfLine, startOfLineIndex] = getStartOfLineWhitespaceOrBlockquoteLevel(text, start);
   if (startOfLine.trim() !== '') {
     const isCallout = calloutRegex.test(text.substring(start, end));
-    const newText = makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text, startOfLine, end, isCallout);
+    const newText = makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text, startOfLine, end, isCallout, addingEmptyLinesAroundBlockquotes);
 
-    return makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(newText, startOfLine, startOfLineIndex, isCallout);
+    return makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(newText, startOfLine, startOfLineIndex, isCallout, addingEmptyLinesAroundBlockquotes);
   }
 
   const newText = makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFile(text, end);


Test patch:
diff --git a/__tests__/empty-line-around-code-fences.test.ts b/__tests__/empty-line-around-code-fences.test.ts
index b77b5af..e6baa98 100644
--- a/__tests__/empty-line-around-code-fences.test.ts
+++ b/__tests__/empty-line-around-code-fences.test.ts
@@ -86,5 +86,63 @@ ruleTest({
         2222
       `,
     },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/833
+      testName: 'Make sure that a callout with a code block is not broken apart',
+      before: dedent`
+        > [!abstract]- Mentions
+        > \`\`\`dataview
+        > XXX
+        > \`\`\`
+      `,
+      after: dedent`
+        > [!abstract]- Mentions
+        >
+        > \`\`\`dataview
+        > XXX
+        > \`\`\`
+      `,
+    },
+    { // relates to https://github.com/platers/obsidian-linter/issues/833
+      testName: 'Make sure that a callout with a code block followed by another callout does not get merged',
+      before: dedent`
+        > [!abstract]- Mentions
+        > \`\`\`dataview
+        > XXX
+        > \`\`\`
+        ${''}
+        > [!abstract]- Mentions2
+        >
+        > Other content
+      `,
+      after: dedent`
+        > [!abstract]- Mentions
+        >
+        > \`\`\`dataview
+        > XXX
+        > \`\`\`
+        ${''}
+        > [!abstract]- Mentions2
+        >
+        > Other content
+      `,
+    },
+    { // relates to https://github.com/platers/obsidian-linter/issues/833
+      testName: 'Make sure that a nested callout with a code block does not get broken apart',
+      before: dedent`
+        > [!abstract]- Mentions
+        > > [!abstract]- Mentions2
+        > > \`\`\`dataview
+        > > XXX
+        > > \`\`\`
+      `,
+      after: dedent`
+        > [!abstract]- Mentions
+        > > [!abstract]- Mentions2
+        > >
+        > > \`\`\`dataview
+        > > XXX
+        > > \`\`\`
+      `,
+    },
   ],
 });