Repository: platers/obsidian-linter (ts)

Issue:
FR: Ensure single space after blockquote `>` angle brackets
## Is Your Feature Request Related to a Problem? Please Describe.

I have one small issue I've just noticed related to blockquotes and our conversations in (#910). Not sure if it's intentional or not, and apologies if it has already been mentioned:

```markdown
>  Text
```

Doesn't lint as:

```markdown
> Text
```

Is there a reason why the >=2 spaces don't get reformatted as a single space? Maybe it's a compromise of some sort? Perhaps it's because not all people use spaces after blockquote angle brackets, e.g. `>> Text` or even `>>Text`.

The problem exists with plain lines beginning with a space too which lead me to find your options to "Remove space around/before/after characters". These could probably mitigate it (but as noted, whilst also risking Markdown formatting in some cases).

I mentioned a similar issue with tables, unsure if any relation: #951

## Describe the Solution You'd Like

I guess my only issue then is that maybe removing >1 spaces could be a distinct rule for blockquotes since we already have the "Space after list markers" option.

Consider adding "Space after _blockquote_ markers" to ensure a single space. Or more flexibly, make it a choice between ensuring no spaces (e.g. `>Text`), no spaces but one after final `>` (e.g. `>> Text`), and ensure space after each `>` (e.g. `> > Text`). If a blockquote `>` is followed by only whitespace, "Removes extra spaces after every line." should trim it.

You've recently done a lot to fix blockquote issues, so I don't expect you to be too enthusiastic at this time, but this issue is here for if/when that time comes.

Thanks!

Pull request description:
Add Support for Checklist Items and List Items for `Blockquote Style`
Fixes #961 

It was noted that we probably should not remove spaces prior to a list item when dealing with blockquote style. As such a check for them has been added which changes how the blockquote style is modified. If it is a list item or checklist line, we just ignore multiple spaces in a row after the last `>` in the blockquote indicators.

Changes Made:
- Handled list item and checklist item lines in blockquotes as special
- Allowed custom checklist values in `Remove Empty List Markers`
- Added a new regex that can be used to identify list item and checklist indicators

Patch:
diff --git a/src/rules/blockquote-style.ts b/src/rules/blockquote-style.ts
index 37c2cfa..ab45e4a 100644
--- a/src/rules/blockquote-style.ts
+++ b/src/rules/blockquote-style.ts
@@ -4,6 +4,7 @@ import RuleBuilder, {DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase} f
 import dedent from 'ts-dedent';
 import {IgnoreTypes} from '../utils/ignore-types';
 import {getStartOfLineWhitespaceOrBlockquoteLevel, replaceTextBetweenStartAndEndWithNewValue} from '../utils/strings';
+import {startsWithListMarkerRegex} from '../utils/regex';
 
 type BlockquoteStyleValues = 'no space' | 'space';
 
@@ -36,15 +37,25 @@ export default class BlockquoteStyle extends RuleBuilder<BlockquoteStyleOptions>
       return this.updateBlockquoteLines(blockquote, this.removeSpaceFromIndicator);
     });
   }
-  removeSpaceFromIndicator(startOfLine: string): string {
-    return startOfLine.replace(/>( |\t)+/g, '>');
+  removeSpaceFromIndicator(startOfLine: string, isListItemMarkerLine: boolean): string {
+    if (isListItemMarkerLine) {
+      return startOfLine.replace(/>[ \t]+>/g, '>>');
+    }
+
+    return startOfLine.replace(/>[ \t]+/g, '>');
   }
-  addSpaceToIndicator(startOfLine: string): string {
+  addSpaceToIndicator(startOfLine: string, isListItemMarkerLine: boolean): string {
     // first we add spaces to blockquote indicators that are not followed by a space and then to catch any that were not handled already
     // we make sure to add a space between any 2 indicators that are side by side
-    return startOfLine.replace(/>([^ \t]|$)/g, '> $1').replace(/>>/g, '> >').replace(/>(?:[ \t]{2,}|\t+)/g, '> ');
+    const newStartOfLine = startOfLine.replace(/>([^ ]|$)/g, '> $1').replace(/>>/g, '> >');
+    if (isListItemMarkerLine) {
+      return newStartOfLine;
+    }
+
+    // since we are not dealing with a list item or checklist line, we can go ahead and remove multiple spaces
+    return newStartOfLine.replace(/>(?:[ \t]{2,}|\t+)/g, '> ');
   }
-  updateBlockquoteLines(blockquote: string, startOfLineModification: (startOfLine: string) => string): string {
+  updateBlockquoteLines(blockquote: string, startOfLineModification: (startOfLine: string, isListMarker: boolean) => string): string {
     let currentIndex = 0;
     let nextNewLine = 0;
     let startOfLine = '';
@@ -60,7 +71,16 @@ export default class BlockquoteStyle extends RuleBuilder<BlockquoteStyleOptions>
       }
 
       [startOfLine, startOfIndex] = getStartOfLineWhitespaceOrBlockquoteLevel(newBlockquote, nextNewLine-1);
-      updatedStartOfLine = startOfLineModification(startOfLine);
+      const startOfRestOfLine = startOfIndex + startOfLine.length+1;
+
+      let endOfRestOfLine = nextNewLine;
+      if (breakOutOfLoop) {
+        endOfRestOfLine++;
+      }
+
+      const restOfLine = newBlockquote.substring(startOfRestOfLine, endOfRestOfLine);
+      const isListItemMarker = startsWithListMarkerRegex.test(restOfLine);
+      updatedStartOfLine = startOfLineModification(startOfLine, isListItemMarker);
 
       // since start of index refers to where the new line character is
       startOfIndex++;
diff --git a/src/rules/remove-empty-list-markers.ts b/src/rules/remove-empty-list-markers.ts
index fcd14ba..53bb1f3 100644
--- a/src/rules/remove-empty-list-markers.ts
+++ b/src/rules/remove-empty-list-markers.ts
@@ -20,7 +20,7 @@ export default class RemoveEmptyListMarkers extends RuleBuilder<RemoveEmptyListM
     return RemoveEmptyListMarkersOptions;
   }
   apply(text: string, options: RemoveEmptyListMarkersOptions): string {
-    const emptyListMarkerRegex = new RegExp(`^${lineStartingWithWhitespaceOrBlockquoteTemplate}(-|\\*|\\+|\\d+[.)]|- (\\[( |x)\\]))\\s*?$`, 'gm');
+    const emptyListMarkerRegex = new RegExp(`^${lineStartingWithWhitespaceOrBlockquoteTemplate}(-|\\*|\\+|\\d+[.)]|- (\\[(.)\\]))\\s*?$`, 'gm');
     // remove all empty list markers followed by a new line
     text = text.replace(new RegExp(emptyListMarkerRegex.source + '\\n', 'gm'), '');
     // remove all empty list markers proceeded by a new line
diff --git a/src/utils/regex.ts b/src/utils/regex.ts
index ecc689c..d81ebdc 100644
--- a/src/utils/regex.ts
+++ b/src/utils/regex.ts
@@ -43,6 +43,8 @@ export const checklistBoxStartsTextRegex = new RegExp(`^${checklistBoxIndicator}
 export const indentedOrBlockquoteNestedChecklistIndicatorRegex = new RegExp(`^${lineStartingWithWhitespaceOrBlockquoteTemplate}- ${checklistBoxIndicator} `);
 export const nonBlockquoteChecklistRegex = new RegExp(`^\\s*- ${checklistBoxIndicator} `);
 
+export const startsWithListMarkerRegex = new RegExp(`^\\s*(-|\\*|\\+|\\d+[.)]|- (${checklistBoxIndicator}))`, 'm');
+
 export const footnoteDefinitionIndicatorAtStartOfLine = /^(\[\^\w+\]) ?([,.;!:?])/gm;
 export const calloutRegex = /^(>\s*)+\[![^\s]*\]/m;
 


Test patch:
diff --git a/__tests__/blockquote-style.test.ts b/__tests__/blockquote-style.test.ts
index fccf64f..5841e3d 100644
--- a/__tests__/blockquote-style.test.ts
+++ b/__tests__/blockquote-style.test.ts
@@ -45,5 +45,57 @@ ruleTest({
       `,
       options: {style: 'space'},
     },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/961
+      testName: 'Multiple spaces after a blockquote indicator is not converted into one space when it is a list item or checklist line when `style=space`',
+      before: dedent`
+        >   Text here
+        >   - List item 1
+        >\t * List item 2
+        >   + List item 3
+        >   1. Ordered item 1
+        >   2) Ordered item 2
+        >   - [ ] Checklist item
+        >- List item 4
+      `,
+      after: dedent`
+        > Text here
+        >   - List item 1
+        > \t * List item 2
+        >   + List item 3
+        >   1. Ordered item 1
+        >   2) Ordered item 2
+        >   - [ ] Checklist item
+        > - List item 4
+      `,
+      options: {style: 'space'},
+    },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/961
+      testName: 'Multiple spaces after a blockquote indicator should be removed except when dealing with list mark lines when `style=no space`',
+      before: dedent`
+        >   Text here
+        >   - List item 1
+        >\t * List item 2
+        >   + List item 3
+        >   1. Ordered item 1
+        >   2) Ordered item 2
+        >   - [ ] Checklist item
+        >- List item 4
+        > >  >   \t > - List item 5
+        > >  >   \t > > - List item 6
+      `,
+      after: dedent`
+        >Text here
+        >   - List item 1
+        >\t * List item 2
+        >   + List item 3
+        >   1. Ordered item 1
+        >   2) Ordered item 2
+        >   - [ ] Checklist item
+        >- List item 4
+        >>>> - List item 5
+        >>>>> - List item 6
+      `,
+      options: {style: 'no space'},
+    },
   ],
 });