Repository: platers/obsidian-linter (ts)

Issue:
FR: `Remove Space around Fullwidth Characters` should be more configurable
## Is Your Feature Request Related to a Problem? Please Describe.

Currently rule `Remove Space around Fullwidth Characters` just removes spaces around some set of characters. 

My concern are `en dash` (`–`, `U+2013`) and `em dash` (`—`, `U+2014`) characters.

The [rules](https://www.scribbr.com/language-rules/dashes/) about spaces around those characters differ. Some guides recommend to don't have spaces around those characters, other recommend to have spaces.

## Describe the Solution You'd Like

We need to be able configure the rule to exclude certain characters from removing spaces.

**Please include an example where applicable:**

``` markdown
— Hi, John!
— Hi, Mary.
```

in such dialogue cases it is very natural (for me) to keep the space after the `em dash`.

Pull request:
Customize RemoveSpaceAroundCharacters rule
Fixes #493 

Patch:
diff --git a/README.md b/README.md
index 1affc4b..945a8f9 100644
--- a/README.md
+++ b/README.md
@@ -95,7 +95,7 @@ Each rule is its own set of logic and is designed to be run independently. This
 - [paragraph-blank-lines](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#paragraph-blank-lines)
 - [remove-empty-lines-between-list-markers-and-checklists](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#remove-empty-lines-between-list-markers-and-checklists)
 - [remove-link-spacing](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#remove-link-spacing)
-- [remove-space-around-fullwidth-characters](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#remove-space-around-fullwidth-characters)
+- [remove-space-around-characters](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#remove-space-around-characters)
 - [space-after-list-markers](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#space-after-list-markers)
 - [space-between-chinese-japanese-or-korean-and-english-or-numbers](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#space-between-chinese-japanese-or-korean-and-english-or-numbers)
 - [trailing-spaces](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#trailing-spaces)
diff --git a/docs/rules.md b/docs/rules.md
index 9dd7d56..333a7dd 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -3308,13 +3308,21 @@ After:
 [[link_here]]
 ``````
 
-### Remove Space around Fullwidth Characters
+### Remove Space around Characters
 
-Alias: `remove-space-around-fullwidth-characters`
-
-Ensures that fullwidth characters are not followed by whitespace (either single spaces or a tab). Note that this may causes issues with markdown format in some cases.
+Alias: `remove-space-around-characters`
 
+Ensures that certain characters are not surrounded by whitespace (either single spaces or a tab). Note that this may causes issues with markdown format in some cases.
 
+Options:
+- Include Fullwidth Forms: Include <a href="https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)">Fullwidth Forms Unicode block</a>
+	- Default: `true`
+- Include CJK Symbols and Punctuation: Include <a href="https://en.wikipedia.org/wiki/CJK_Symbols_and_Punctuation">CJK Symbols and Punctuation Unicode block</a>
+	- Default: `true`
+- Include Dashes: Include en dash (–) and em dash (—)
+	- Default: `true`
+- Other symbols: Other symbols to include
+	- Default: ``
 
 Example: Remove Spaces and Tabs around Fullwidth Characters
 
diff --git a/src/rules/file-name-heading.ts b/src/rules/file-name-heading.ts
index 2456471..4f82806 100644
--- a/src/rules/file-name-heading.ts
+++ b/src/rules/file-name-heading.ts
@@ -36,13 +36,7 @@ export default class FileNameHeading extends RuleBuilder<FileNameHeadingOptions>
       let yaml_end = text.indexOf('\n---');
       yaml_end =
         yaml_end == -1 || !text.startsWith('---\n') ? 0 : yaml_end + 5;
-
-      let header = `# ${fileName}\n`;
-      if (text.length < yaml_end) {
-        header = '\n' + header;
-      }
-
-      return insert(text, yaml_end, header);
+      return insert(text, yaml_end, `# ${fileName}\n`);
     });
   }
   get exampleBuilders(): ExampleBuilder<FileNameHeadingOptions>[] {
diff --git a/src/rules/remove-space-around-fullwidth-characters.ts b/src/rules/remove-space-around-characters.ts
similarity index 63%
rename from src/rules/remove-space-around-fullwidth-characters.ts
rename to src/rules/remove-space-around-characters.ts
index 6811f64..b9c24f7 100644
--- a/src/rules/remove-space-around-fullwidth-characters.ts
+++ b/src/rules/remove-space-around-characters.ts
@@ -1,29 +1,54 @@
 import {Options, RuleType} from '../rules';
-import RuleBuilder, {ExampleBuilder, OptionBuilderBase} from './rule-builder';
+import RuleBuilder, {BooleanOptionBuilder, ExampleBuilder, OptionBuilderBase, TextOptionBuilder} from './rule-builder';
 import dedent from 'ts-dedent';
 import {ignoreListOfTypes, IgnoreTypes} from '../utils/ignore-types';
 import {updateListItemText} from '../utils/mdast';
+import {escapeRegExp} from '../utils/regex';
 
-class RemoveSpaceAroundFullwidthCharactersOptions implements Options {
+class RemoveSpaceAroundCharactersOptions implements Options {
+  includeFullwidthForms: boolean = true;
+  includeCJKSymbolsAndPunctuation: boolean = true;
+  includeDashes: boolean = true;
+  otherSymbols: string = '';
 }
 
 @RuleBuilder.register
-export default class RemoveSpaceAroundFullwidthCharacters extends RuleBuilder<RemoveSpaceAroundFullwidthCharactersOptions> {
-  get OptionsClass(): new () => RemoveSpaceAroundFullwidthCharactersOptions {
-    return RemoveSpaceAroundFullwidthCharactersOptions;
+export default class RemoveSpaceAroundCharacters extends RuleBuilder<RemoveSpaceAroundCharactersOptions> {
+  get OptionsClass(): new () => RemoveSpaceAroundCharactersOptions {
+    return RemoveSpaceAroundCharactersOptions;
   }
   get name(): string {
-    return 'Remove Space around Fullwidth Characters';
+    return 'Remove Space around Characters';
   }
   get description(): string {
-    return 'Ensures that fullwidth characters are not followed by whitespace (either single spaces or a tab). Note that this may causes issues with markdown format in some cases.';
+    return 'Ensures that certain characters are not surrounded by whitespace (either single spaces or a tab). Note that this may causes issues with markdown format in some cases.';
   }
   get type(): RuleType {
     return RuleType.SPACING;
   }
-  apply(text: string, options: RemoveSpaceAroundFullwidthCharactersOptions): string {
-    const fullwidthCharacterWithTextAtStart = /([ \t])+([\u2013\u2014\u2026\u3001\u3002\u300a\u300d-\u300f\u3014\u3015\u3008-\u3011\uff00-\uffff])/g;
-    const fullwidthCharacterWithTextAtEnd = /([\u2013\u2014\u2026\u3001\u3002\u300a\u300d-\u300f\u3014\u3015\u3008-\u3011\uff00-\uffff])([ \t])+/g;
+  apply(text: string, options: RemoveSpaceAroundCharactersOptions): string {
+    let symbolsRegExpBuilder = '';
+
+    if (options.includeFullwidthForms) {
+      symbolsRegExpBuilder += '\uff01-\uff5e';
+    }
+
+    if (options.includeCJKSymbolsAndPunctuation) {
+      symbolsRegExpBuilder += '\u3000-\u30ff';
+    }
+
+    if (options.includeDashes) {
+      symbolsRegExpBuilder += '\u2013\u2014';
+    }
+
+    symbolsRegExpBuilder += escapeRegExp(options.otherSymbols);
+
+    if (!symbolsRegExpBuilder) {
+      return text;
+    }
+
+    const fullwidthCharacterWithTextAtStart = new RegExp(`([ \t])+([${symbolsRegExpBuilder}])`, 'g');
+    const fullwidthCharacterWithTextAtEnd = new RegExp(`([${symbolsRegExpBuilder}])([ \t])+`, 'g');
 
     const replaceWhitespaceAroundFullwidthCharacters = function(text: string): string {
       return text.replace(fullwidthCharacterWithTextAtStart, '$2').replace(fullwidthCharacterWithTextAtEnd, '$1');
@@ -35,7 +60,7 @@ export default class RemoveSpaceAroundFullwidthCharacters extends RuleBuilder<Re
 
     return newText;
   }
-  get exampleBuilders(): ExampleBuilder<RemoveSpaceAroundFullwidthCharactersOptions>[] {
+  get exampleBuilders(): ExampleBuilder<RemoveSpaceAroundCharactersOptions>[] {
     return [
       new ExampleBuilder({
         description: 'Remove Spaces and Tabs around Fullwidth Characters',
@@ -111,7 +136,32 @@ export default class RemoveSpaceAroundFullwidthCharacters extends RuleBuilder<Re
       }),
     ];
   }
-  get optionBuilders(): OptionBuilderBase<RemoveSpaceAroundFullwidthCharactersOptions>[] {
-    return [];
+  get optionBuilders(): OptionBuilderBase<RemoveSpaceAroundCharactersOptions>[] {
+    return [
+      new BooleanOptionBuilder({
+        name: 'Include Fullwidth Forms',
+        description: 'Include <a href="https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)">Fullwidth Forms Unicode block</a>',
+        OptionsClass: RemoveSpaceAroundCharactersOptions,
+        optionsKey: 'includeFullwidthForms',
+      }),
+      new BooleanOptionBuilder({
+        name: 'Include CJK Symbols and Punctuation',
+        description: 'Include <a href="https://en.wikipedia.org/wiki/CJK_Symbols_and_Punctuation">CJK Symbols and Punctuation Unicode block</a>',
+        OptionsClass: RemoveSpaceAroundCharactersOptions,
+        optionsKey: 'includeCJKSymbolsAndPunctuation',
+      }),
+      new BooleanOptionBuilder({
+        name: 'Include Dashes',
+        description: 'Include en dash (–) and em dash (—)',
+        OptionsClass: RemoveSpaceAroundCharactersOptions,
+        optionsKey: 'includeDashes',
+      }),
+      new TextOptionBuilder({
+        name: 'Other symbols',
+        description: 'Other symbols to include',
+        OptionsClass: RemoveSpaceAroundCharactersOptions,
+        optionsKey: 'otherSymbols',
+      }),
+    ];
   }
 }


Test patch:
diff --git a/__tests__/file-name-heading.test.ts b/__tests__/file-name-heading.test.ts
index 9f3a022..79d2728 100644
--- a/__tests__/file-name-heading.test.ts
+++ b/__tests__/file-name-heading.test.ts
@@ -26,23 +26,5 @@ ruleTest({
         fileName: 'File Name',
       },
     },
-    { // accounts for https://github.com/platers/obsidian-linter/issues/513
-      testName: 'Inserts the heading on the line after the frontmatter of the file when it is the only thing in the file',
-      before: dedent`
-        ---
-        hello: test
-        ---
-      `,
-      after: dedent`
-        ---
-        hello: test
-        ---
-        # Test note
-        ${''}
-      `,
-      options: {
-        fileName: 'Test note',
-      },
-    },
   ],
 });
diff --git a/__tests__/remove-space-around-characters.test.ts b/__tests__/remove-space-around-characters.test.ts
new file mode 100644
index 0000000..0b9f721
--- /dev/null
+++ b/__tests__/remove-space-around-characters.test.ts
@@ -0,0 +1,73 @@
+import RemoveSpaceAroundCharacters from '../src/rules/remove-space-around-characters';
+import dedent from 'ts-dedent';
+import {ruleTest} from './common';
+
+ruleTest({
+  RuleBuilderClass: RemoveSpaceAroundCharacters,
+  testCases: [
+    {
+      // accounts for https://github.com/platers/obsidian-linter/issues/344
+      testName: 'Make sure that lists with a dollar sign in them actually get escaped correctly',
+      before: dedent`
+        # Title
+        ${''}
+        - lorem \`$\` ipsum
+        ${''}
+      `,
+      after: dedent`
+        # Title
+        ${''}
+        - lorem \`$\` ipsum
+        ${''}
+      `,
+    },
+    {
+      testName: 'Fullwidth characters can be excluded',
+      before: dedent`
+        Spaces around fullwidth Ａ are preserved
+      `,
+      after: dedent`
+        Spaces around fullwidth Ａ are preserved
+      `,
+      options: {
+        includeFullwidthForms: false,
+      },
+    },
+    {
+      testName: 'CJK symbols and punctuations can be excluded',
+      before: dedent`
+        Spaces around 《 are preserved
+      `,
+      after: dedent`
+        Spaces around 《 are preserved
+      `,
+      options: {
+        includeCJKSymbolsAndPunctuation: false,
+      },
+    },
+    {
+      testName: 'Dashes can be excluded',
+      before: dedent`
+        Spaces around en dash – and em dash — are preserved
+      `,
+      after: dedent`
+        Spaces around en dash – and em dash — are preserved
+      `,
+      options: {
+        includeDashes: false,
+      },
+    },
+    {
+      testName: 'Custom symbols can be added',
+      before: dedent`
+        Spaces around custom symbols : are ; removed
+      `,
+      after: dedent`
+        Spaces around custom symbols:are;removed
+      `,
+      options: {
+        otherSymbols: ':;',
+      },
+    },
+  ],
+});
diff --git a/__tests__/remove-space-around-fullwidth-characters.test.ts b/__tests__/remove-space-around-fullwidth-characters.test.ts
deleted file mode 100644
index 2d82274..0000000
--- a/__tests__/remove-space-around-fullwidth-characters.test.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import RemoveSpaceAroundFullWidthCharacters from '../src/rules/remove-space-around-fullwidth-characters';
-import dedent from 'ts-dedent';
-import {ruleTest} from './common';
-
-ruleTest({
-  RuleBuilderClass: RemoveSpaceAroundFullWidthCharacters,
-  testCases: [
-    {
-      // accounts for https://github.com/platers/obsidian-linter/issues/344
-      testName: 'Make sure that lists with a dollar sign in them actually get escaped correctly',
-      before: dedent`
-        # Title
-        ${''}
-        - lorem \`$\` ipsum
-        ${''}
-      `,
-      after: dedent`
-        # Title
-        ${''}
-        - lorem \`$\` ipsum
-        ${''}
-      `,
-    },
-  ],
-});