Repository: platers/obsidian-linter (ts)

Issue:
FR: Format YAML 'tags' Key 
sometimes we have

tags: tag1 tag2 tag3
Tags: tag1, tag2, tag3
tags: 
       - tag1
       - tag2
       - tag3

but would be good if they can be linted to rules, or proper yaml

tags: [tag1, tag2, tag3]


FR: Convert Aliases to Multiline Array
The option so that you can do from this:
```
---
aliases: h1
---
```
This:
```
---
aliases: 
- h1
---
```
FR: Format YAML Arrays
I would like a feature that unwraps yaml collections in frontmatter (per example 2.3 [here](https://yaml.org/spec/1.2.2/#21-collections)). This was an option on an older obsidian linter.

Example:

```
---
tags: [red, yellow, green]
aliases: [Note, 'Note , Comma', BigNote]
===
```

would convert to:

```
---
tags:
  - red
  - yellow
  - green
aliases:
 - Note
 - Note , Comma
 - BigNote
---
```

The [dataview plugin](https://github.com/blacksmithgu/obsidian-dataview) allows arbitrary fields in frontmatter, so this functionality would not be limited to fields recognized by Obsidian. (Perhaps there would be an option to list the fields that should be unwrapped)

## Describe Alternatives You've Considered

I have not been able to think of any alternatives other than manually doing this.



Pull request description:
Add Rule to Format Yaml Arrays
Fixes #172 
Fixes #291
Fixes #182 

Changes Made:
- Added a rule to account for updating yaml array formats and forcing a regular string value to be a yaml array
- Added a bunch of tests to handle some possible test cases
- Added a couple of examples
- Added a fix for a bug where if a url had `[` in it, it was considered to be a regular markdown link which caused multiple `<` to be added at the start of the link when the rule for removing space around link text was active


Patch:
diff --git a/README.md b/README.md
index 0c0e57a..1379e35 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ Documentation for all rules can be found in the [rules docs](https://github.com/
 
 - [escape-yaml-special-characters](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#escape-yaml-special-characters)
 - [format-tags-in-yaml](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#format-tags-in-yaml)
+- [format-yaml-array](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#format-yaml-array)
 - [insert-yaml-attributes](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#insert-yaml-attributes)
 - [remove-yaml-keys](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#remove-yaml-keys)
 - [yaml-key-sort](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#yaml-key-sort)
diff --git a/docs/rules.md b/docs/rules.md
index ccb98ee..72c8d6b 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -186,6 +186,103 @@ tags:
 ---
 ``````
 
+### Format Yaml Array
+
+Alias: `format-yaml-array`
+
+Allows for the formatting of regular yaml arrays as either multi-line or single-line and `tags` and `aliases` are allowed to have some Obsidian specific yaml formats. Note that single string to single-line goes from a single string entry to a single-line array if more than 1 entry is present. The same is true for single string to multi-line except it becomes a multi-line array.
+
+Options:
+- Yaml aliases section style: The style of the yaml aliases section
+	- Default: `single-line`
+	- `multi-line`: ```aliases:\n  - Title```
+	- `single-line`: ```aliases: [Title]```
+	- `single string comma delimited`: ```aliases: Title, Other Title```
+	- `single string to single-line`: Aliases will be formatted as a string if there is 1 or fewer elements like so ```aliases: Title```. If there is more than 1 element, it will be formatted like a single-line array.
+	- `single string to multi-line`: Aliases will be formatted as a string if there is 1 or fewer elements like so ```aliases: Title```. If there is more than 1 element, it will be formatted like a multi-line array.
+- Format yaml aliases section: Turns on formatting for the yaml aliases section
+	- Default: `true`
+- Yaml tags section style: The style of the yaml tags section
+	- Default: `single-line`
+	- `multi-line`: ```tags:\n  - tag1```
+	- `single-line`: ```tags: [tag1]```
+	- `single string to single-line`: Tags will be formatted as a string if there is 1 or fewer elements like so ```tags: tag1```. If there is more than 1 element, it will be formatted like a single-line array.
+	- `single string to multi-line`: Aliases will be formatted as a string if there is 1 or fewer elements like so ```tags: tag1```. If there is more than 1 element, it will be formatted like a multi-line array.
+	- `single-line space delimited`: ```tags: [tag1 tag2]```
+	- `single string space delimited`: ```tags: tag1 tag2```
+	- `single string comma delimited`: ```tags: tag1, tag2```
+- Default yaml array section style: The style of other yaml arrays that are not `tags`, `aliases` or  in `Force key values to be single-line arrays` and `Force key values to be multi-line arrays`
+	- Default: `single-line`
+	- `multi-line`: ```key:\n  - value```
+	- `single-line`: ```key: [value]```
+- Format yaml array sections: Turns on formatting for regular yaml arrays
+	- Default: `true`
+- Force key values to be single-line arrays: Forces the yaml array for the new line separated keys to be in single-line format (leave empty to disable this option)
+	- Default: ``
+- Force key values to be multi-line arrays: Forces the yaml array for the new line separated keys to be in multi-line format (leave empty to disable this option)
+	- Default: ``
+
+Example: Format tags as a single-line array delimited by spaces and aliases as a multi-line array and format the key `test` to be a single-line array
+
+Before:
+
+``````markdown
+---
+tags:
+  - computer
+  - research
+aliases: Title 1, Title2
+test: this is a value
+---
+
+# Notes:
+
+Nesting yaml arrays may result in unexpected results.
+
+Multi-line arrays will have empty values removed only leaving one if it is completely empty. The same is not true for single-line arrays as that is invalid yaml unless it comes as the last entry in the array.
+``````
+
+After:
+
+``````markdown
+---
+tags: [computer, research]
+aliases:
+  - Title 1
+  - Title2
+test: [this is a value]
+---
+
+# Notes:
+
+Nesting yaml arrays may result in unexpected results.
+
+Multi-line arrays will have empty values removed only leaving one if it is completely empty. The same is not true for single-line arrays as that is invalid yaml unless it comes as the last entry in the array.
+``````
+Example: Format tags as a single string with space delimiters, ignore aliases, and format regular yaml arrays as single-line arrays
+
+Before:
+
+``````markdown
+---
+aliases: Typescript
+types:
+  - thought provoking
+  - peer reviewed
+tags: [computer, science, trajectory]
+---
+``````
+
+After:
+
+``````markdown
+---
+aliases: Typescript
+types: [thought provoking, peer reviewed]
+tags: computer science trajectory
+---
+``````
+
 ### Insert YAML attributes
 
 Alias: `insert-yaml-attributes`
diff --git a/package.json b/package.json
index 7752685..c926788 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
     "dev": "rollup --config rollup.config.js -w",
     "build": "rollup --config rollup.config.js --environment BUILD:production && terser main.js --compress toplevel=true,passes=2 --ecma 2016 --comments \"/(GENERATED|Copyright|license)/\" --output main.js",
     "test": "jest",
+    "test-suite": "jest -t \"$1\"",
     "docs": "node docs.js",
     "compile": "npm run build && npm run docs && npm run lint && npm run test",
     "lint": "eslint . --ext .ts --fix"
diff --git a/src/main.ts b/src/main.ts
index 9011f38..67b4f52 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -248,7 +248,6 @@ export default class LinterPlugin extends Plugin {
     // escape YAML where possible before parsing yaml
     [newText] = EscapeYamlSpecialCharacters.applyIfEnabled(newText, this.settings, disabledRules);
 
-
     const modifiedAtTime = moment(file.stat.mtime, this.momentLocale).format();
     const createdAtTime = moment(file.stat.ctime, this.momentLocale).format();
 
diff --git a/src/rules/format-yaml-arrays.ts b/src/rules/format-yaml-arrays.ts
new file mode 100644
index 0000000..2ed5b4e
--- /dev/null
+++ b/src/rules/format-yaml-arrays.ts
@@ -0,0 +1,263 @@
+import {Options, RuleType} from '../rules';
+import RuleBuilder, {BooleanOptionBuilder, DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
+import dedent from 'ts-dedent';
+import {convertAliasValueToStringOrStringArray,
+  convertTagValueToStringOrStringArray,
+  formatYAML,
+  formatYamlArrayValue,
+  getYamlSectionValue,
+  loadYAML,
+  NormalYamlArrayFormats,
+  setYamlSection,
+  SpecialYamlArrayFormats,
+  splitValueIfSingleOrMultilineArray,
+  TagSpecificYamlArrayFormats} from '../utils/yaml';
+
+class FormatYamlArrayOptions implements Options {
+  aliasArrayStyle?: NormalYamlArrayFormats | SpecialYamlArrayFormats = 'single-line';
+  formatAliasKey?: boolean = true;
+  tagArrayStyle?: TagSpecificYamlArrayFormats | NormalYamlArrayFormats | SpecialYamlArrayFormats = 'single-line';
+  formatTagKey?: boolean = true;
+  defaultArrayStyle?: NormalYamlArrayFormats = 'single-line';
+  formatArrayKeys?: boolean = true;
+  forceSingleLineArrayStyle?: string[] = [];
+  forceMultiLineArrayStyle?: string[] = [];
+}
+
+@RuleBuilder.register
+export default class RuleTemplate extends RuleBuilder<FormatYamlArrayOptions> {
+  get OptionsClass(): new () => FormatYamlArrayOptions {
+    return FormatYamlArrayOptions;
+  }
+  get name(): string {
+    return 'Format Yaml Array';
+  }
+  get description(): string {
+    return 'Allows for the formatting of regular yaml arrays as either multi-line or single-line and `tags` and `aliases` are allowed to have some Obsidian specific yaml formats. Note that single string to single-line goes from a single string entry to a single-line array if more than 1 entry is present. The same is true for single string to multi-line except it becomes a multi-line array.';
+  }
+  get type(): RuleType {
+    return RuleType.YAML;
+  }
+  apply(text: string, options: FormatYamlArrayOptions): string {
+    return formatYAML(text, (text: string) => {
+      const obsidianTagKey = 'tags';
+      const obsidianAliasKey = 'aliases';
+
+      const yaml = loadYAML(text.replace('---\n', '').replace('\n---', ''));
+      if (!yaml) {
+        return text;
+      }
+
+      if (options.formatAliasKey && Object.keys(yaml).includes(obsidianAliasKey)) {
+        text = setYamlSection(text, obsidianAliasKey, formatYamlArrayValue(convertAliasValueToStringOrStringArray(splitValueIfSingleOrMultilineArray(getYamlSectionValue(text, obsidianAliasKey))), options.aliasArrayStyle));
+      }
+
+      if (options.formatTagKey && Object.keys(yaml).includes(obsidianTagKey)) {
+        text = setYamlSection(text, obsidianTagKey, formatYamlArrayValue(convertTagValueToStringOrStringArray(splitValueIfSingleOrMultilineArray(getYamlSectionValue(text, obsidianTagKey))), options.tagArrayStyle));
+      }
+
+      if (options.formatArrayKeys) {
+        const keysToIgnore = [obsidianAliasKey, obsidianTagKey, ...options.forceMultiLineArrayStyle, ...options.forceSingleLineArrayStyle];
+
+        for (const key of Object.keys(yaml)) {
+          // skip non-arrays and already accounted for keys
+          if (keysToIgnore.includes(key) || !(typeof yaml[key] === 'object' && yaml[key] as string[])) {
+            continue;
+          }
+
+          text = setYamlSection(text, key, formatYamlArrayValue(splitValueIfSingleOrMultilineArray(getYamlSectionValue(text, key)), options.defaultArrayStyle));
+        }
+      }
+
+      for (const singleLineArrayKey of options.forceSingleLineArrayStyle) {
+        if (!Object.keys(yaml).includes(singleLineArrayKey)) {
+          continue;
+        }
+
+        text = setYamlSection(text, singleLineArrayKey, formatYamlArrayValue(splitValueIfSingleOrMultilineArray(getYamlSectionValue(text, singleLineArrayKey)), 'single-line'));
+      }
+
+      for (const multiLineArrayKey of options.forceMultiLineArrayStyle) {
+        if (!Object.keys(yaml).includes(multiLineArrayKey)) {
+          continue;
+        }
+
+        text = setYamlSection(text, multiLineArrayKey, formatYamlArrayValue(splitValueIfSingleOrMultilineArray(getYamlSectionValue(text, multiLineArrayKey)), 'multi-line'));
+      }
+
+      return text;
+    });
+  }
+  get exampleBuilders(): ExampleBuilder<FormatYamlArrayOptions>[] {
+    return [
+      new ExampleBuilder({
+        description: 'Format tags as a single-line array delimited by spaces and aliases as a multi-line array and format the key `test` to be a single-line array',
+        before: dedent`
+          ---
+          tags:
+            - computer
+            - research
+          aliases: Title 1, Title2
+          test: this is a value
+          ---
+          ${''}
+          # Notes:
+          ${''}
+          Nesting yaml arrays may result in unexpected results.
+          ${''}
+          Multi-line arrays will have empty values removed only leaving one if it is completely empty. The same is not true for single-line arrays as that is invalid yaml unless it comes as the last entry in the array.
+        `,
+        after: dedent`
+          ---
+          tags: [computer, research]
+          aliases:
+            - Title 1
+            - Title2
+          test: [this is a value]
+          ---
+          ${''}
+          # Notes:
+          ${''}
+          Nesting yaml arrays may result in unexpected results.
+          ${''}
+          Multi-line arrays will have empty values removed only leaving one if it is completely empty. The same is not true for single-line arrays as that is invalid yaml unless it comes as the last entry in the array.
+        `,
+        options: {
+          aliasArrayStyle: 'multi-line',
+          forceSingleLineArrayStyle: ['test'],
+        },
+      }),
+      new ExampleBuilder({
+        description: 'Format tags as a single string with space delimiters, ignore aliases, and format regular yaml arrays as single-line arrays',
+        before: dedent`
+          ---
+          aliases: Typescript
+          types:
+            - thought provoking
+            - peer reviewed
+          tags: [computer, science, trajectory]
+          ---
+        `,
+        after: dedent`
+          ---
+          aliases: Typescript
+          types: [thought provoking, peer reviewed]
+          tags: computer science trajectory
+          ---
+        `,
+        options: {
+          formatAliasKey: false,
+          tagArrayStyle: 'single string space delimited',
+        },
+      }),
+    ];
+  }
+  get optionBuilders(): OptionBuilderBase<FormatYamlArrayOptions>[] {
+    return [
+      new DropdownOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Yaml aliases section style',
+        description: 'The style of the yaml aliases section',
+        optionsKey: 'aliasArrayStyle',
+        records: [
+          {
+            value: 'multi-line',
+            description: '```aliases:\\n  - Title```',
+          },
+          {
+            value: 'single-line',
+            description: '```aliases: [Title]```',
+          },
+          {
+            value: 'single string comma delimited',
+            description: '```aliases: Title, Other Title```',
+          },
+          {
+            value: 'single string to single-line',
+            description: 'Aliases will be formatted as a string if there is 1 or fewer elements like so ```aliases: Title```. If there is more than 1 element, it will be formatted like a single-line array.',
+          },
+          {
+            value: 'single string to multi-line',
+            description: 'Aliases will be formatted as a string if there is 1 or fewer elements like so ```aliases: Title```. If there is more than 1 element, it will be formatted like a multi-line array.',
+          },
+        ],
+      }),
+      new BooleanOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Format yaml aliases section',
+        description: 'Turns on formatting for the yaml aliases section',
+        optionsKey: 'formatAliasKey',
+      }),
+      new DropdownOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Yaml tags section style',
+        description: 'The style of the yaml tags section',
+        optionsKey: 'tagArrayStyle',
+        records: [
+          {
+            value: 'multi-line',
+            description: '```tags:\\n  - tag1```',
+          },
+          {
+            value: 'single-line',
+            description: '```tags: [tag1]```',
+          },
+          {
+            value: 'single string to single-line',
+            description: 'Tags will be formatted as a string if there is 1 or fewer elements like so ```tags: tag1```. If there is more than 1 element, it will be formatted like a single-line array.',
+          },
+          {
+            value: 'single string to multi-line',
+            description: 'Aliases will be formatted as a string if there is 1 or fewer elements like so ```tags: tag1```. If there is more than 1 element, it will be formatted like a multi-line array.',
+          },
+          {
+            value: 'single-line space delimited',
+            description: '```tags: [tag1 tag2]```',
+          },
+          {
+            value: 'single string space delimited',
+            description: '```tags: tag1 tag2```',
+          },
+          {
+            value: 'single string comma delimited',
+            description: '```tags: tag1, tag2```',
+          },
+        ],
+      }),
+      new DropdownOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Default yaml array section style',
+        description: 'The style of other yaml arrays that are not `tags`, `aliases` or  in `Force key values to be single-line arrays` and `Force key values to be multi-line arrays`',
+        optionsKey: 'defaultArrayStyle',
+        records: [
+          {
+            value: 'multi-line',
+            description: '```key:\\n  - value```',
+          },
+          {
+            value: 'single-line',
+            description: '```key: [value]```',
+          },
+        ],
+      }),
+      new BooleanOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Format yaml array sections',
+        description: 'Turns on formatting for regular yaml arrays',
+        optionsKey: 'formatArrayKeys',
+      }),
+      new TextAreaOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Force key values to be single-line arrays',
+        description: 'Forces the yaml array for the new line separated keys to be in single-line format (leave empty to disable this option)',
+        optionsKey: 'forceSingleLineArrayStyle',
+      }),
+      new TextAreaOptionBuilder({
+        OptionsClass: FormatYamlArrayOptions,
+        name: 'Force key values to be multi-line arrays',
+        description: 'Forces the yaml array for the new line separated keys to be in multi-line format (leave empty to disable this option)',
+        optionsKey: 'forceMultiLineArrayStyle',
+      }),
+    ];
+  }
+}
diff --git a/src/utils/ignore-types.ts b/src/utils/ignore-types.ts
index cf8d929..80941ad 100644
--- a/src/utils/ignore-types.ts
+++ b/src/utils/ignore-types.ts
@@ -1,4 +1,4 @@
-import {obsidianMultilineCommentRegex, tagRegex, wikiLinkRegex, yamlRegex, escapeDollarSigns} from './regex';
+import {obsidianMultilineCommentRegex, tagRegex, wikiLinkRegex, yamlRegex, escapeDollarSigns, genericLinkRegex} from './regex';
 import {getPositions, MDAstTypes} from './mdast';
 import type {Position} from 'unist';
 import {replaceTextBetweenStartAndEndWithNewValue} from './strings';
@@ -140,7 +140,7 @@ function replaceMarkdownLinks(text: string, regularLinkPlaceholder: string): Ign
 
     const regularLink = text.substring(position.start.offset, position.end.offset);
     // skip links that are not in markdown format
-    if (!regularLink.includes('[')) {
+    if (!regularLink.match(genericLinkRegex)) {
       continue;
     }
 
diff --git a/src/utils/mdast.ts b/src/utils/mdast.ts
index c58396d..45ecb94 100644
--- a/src/utils/mdast.ts
+++ b/src/utils/mdast.ts
@@ -2,7 +2,7 @@ import {visit} from 'unist-util-visit';
 import type {Position} from 'unist';
 import type {Root} from 'mdast';
 import {replaceTextBetweenStartAndEndWithNewValue} from './strings';
-import {escapeRegExp} from './regex';
+import {escapeRegExp, genericLinkRegex} from './regex';
 import {remark} from 'remark';
 import remarkGfm from 'remark-gfm';
 
@@ -277,7 +277,7 @@ export function removeSpacesInLinkText(text: string): string {
   for (const position of positions) {
     const regularLink = text.substring(position.start.offset, position.end.offset);
     // skip links that are not are not in markdown format
-    if (!regularLink.includes('[')) {
+    if (!regularLink.match(genericLinkRegex)) {
       continue;
     }
 
diff --git a/src/utils/regex.ts b/src/utils/regex.ts
index 504fc49..fc8ca5d 100644
--- a/src/utils/regex.ts
+++ b/src/utils/regex.ts
@@ -9,6 +9,7 @@ export const tildeBlockRegexTemplate = fencedRegexTemplate.replaceAll('X', '~');
 export const indentedBlockRegex = '^((\t|( {4})).*\n)+';
 export const codeBlockRegex = new RegExp(`${backtickBlockRegexTemplate}|${tildeBlockRegexTemplate}|${indentedBlockRegex}`, 'gm');
 export const wikiLinkRegex = /(!?)(\[{2}[^[\n\]]*\]{2})/g;
+export const genericLinkRegex = /^!?\[.*\](.*)$/;
 export const tagRegex = /#[^\s#]{1,}/g;
 export const obsidianMultilineCommentRegex = /^%%\n[^%]*\n%%/gm;
 export const wordSplitterRegex = /[,\s]+/;
diff --git a/src/utils/yaml.ts b/src/utils/yaml.ts
index 71f5d79..03a51b3 100644
--- a/src/utils/yaml.ts
+++ b/src/utils/yaml.ts
@@ -34,7 +34,7 @@ export function toSingleLineArrayYamlString<T>(arr: T[]): string {
 }
 
 function getYamlSectionRegExp(rawKey: string): RegExp {
-  return new RegExp(`(?<=^|\\n)${rawKey}:[ \\t]*(\\S.*|(?:\\n {2}\\S.*)*)\\n`);
+  return new RegExp(`(?<=^|\\n)${rawKey}:[ \\t]*(\\S.*|(?:(?:\\n *- \\S.*)|((?:\\n *- *))*)*)\\n`);
 }
 
 export function setYamlSection(yaml: string, rawKey: string, rawValue: string): string {
@@ -75,3 +75,163 @@ export function loadYAML(yaml_text: string): any {
 
   return parsed_yaml;
 }
+
+export type TagSpecificYamlArrayFormats = 'single string space delimited' | 'single-line space delimited';
+
+export type SpecialYamlArrayFormats = 'single string to single-line' | 'single string to multi-line' | 'single string comma delimited';
+
+export type NormalYamlArrayFormats = 'single-line' | 'multi-line';
+
+/**
+ * Formats the yaml array value passed in with the specified format.
+ * @param {string | string[]} value The value(s) that will be used as the parts of the array that is assumed to already be broken down into the appropriate format to be put in the array.
+ * @param {NormalYamlArrayFormats | SpecialYamlArrayFormats | TagSpecificYamlArrayFormats} format The format that the array should be converted into.
+ * @return {string} The formatted array in the specified yaml/obsidian yaml format.
+ */
+export function formatYamlArrayValue(value: string | string[], format: NormalYamlArrayFormats | SpecialYamlArrayFormats | TagSpecificYamlArrayFormats): string {
+  if (typeof value === 'string') {
+    value = [value];
+  }
+
+  switch (format) {
+    case 'single-line':
+      if (value == null || value.length === 0) {
+        return ' []';
+      }
+
+      return ' ' + convertStringArrayToSingleLineArray(value);
+    case 'multi-line':
+      if (value == null || value.length === 0) {
+        return '\n  - ';
+      }
+      return '\n  - ' + value.join('\n  - ');
+    case 'single string to single-line':
+      if (value == null || value.length === 0) {
+        return ' ';
+      } else if (value.length === 1) {
+        return ' ' + value[0];
+      }
+
+      return ' ' + convertStringArrayToSingleLineArray(value);
+    case 'single string to multi-line':
+      if (value == null || value.length === 0) {
+        return ' ';
+      } else if (value.length === 1) {
+        return ' ' + value[0];
+      }
+
+      return '\n  - ' + value.join('\n  - ');
+    case 'single string space delimited':
+      if (value == null || value.length === 0) {
+        return ' ';
+      } else if (value.length === 1) {
+        return ' ' + value[0];
+      }
+
+      return ' ' +value.join(' ');
+    case 'single string comma delimited':
+      if (value == null || value.length === 0) {
+        return ' ';
+      } else if (value.length === 1) {
+        return ' ' + value[0];
+      }
+
+      return ' ' + value.join(', ');
+    case 'single-line space delimited':
+      if (value == null || value.length === 0) {
+        return ' []';
+      } else if (value.length === 1) {
+        return ' ' + value[0];
+      }
+
+      return ' ' + convertStringArrayToSingleLineArray(value).replaceAll(', ', ' ');
+  }
+}
+
+function convertStringArrayToSingleLineArray(arrayItems: string[]): string {
+  if (arrayItems == null || arrayItems.length === 0) {
+    return '[]';
+  }
+
+  return '[' + arrayItems.join(', ') + ']';
+}
+
+/**
+ * Parses single-line and multi-line arrays into an array that can be used for formatting down the line
+ * @param {string} value The value to see about parsing if it is a sing-line or multi-line array
+ * @return {string|string[]} The original value if it was not a single or multi-line array or the an array of the values from the array (multi-line arrays will have empty values removed)
+ */
+export function splitValueIfSingleOrMultilineArray(value: string): string | string[] {
+  if (value == null || value.length === 0) {
+    return null;
+  }
+
+  value = value.trimEnd();
+  if (value.startsWith('[')) {
+    value = value.substring(1);
+
+    if (value.endsWith(']')) {
+      value = value.substring(0, value.length - 1);
+    }
+
+    // accounts for an empty single line array which can then be converted as needed later on
+    if (value.length === 0) {
+      return null;
+    }
+
+    let arrayItems = value.split(', ');
+
+    arrayItems = arrayItems.length > 1 ? arrayItems : arrayItems[0].split(',');
+
+    return arrayItems.filter((el: string) => {
+      return el != '';
+    });
+  }
+
+  if (value.includes('\n')) {
+    let arrayItems = value.split(/[ \t]*\n[ \t]*-[ \t]*/);
+    arrayItems.splice(0, 1);
+
+    arrayItems = arrayItems.filter((el: string) => {
+      return el != '';
+    });
+
+    if (arrayItems == null || arrayItems.length === 0 ) {
+      return null;
+    }
+
+    return arrayItems;
+  }
+
+  return value;
+}
+
+/**
+ * Converts the tag string to the proper split up values based on whether or not it is already an array and if it has delimiters.
+ * @param {string | string[]} value The value that is already good to go or needs to be split on a comma or spaces.
+ * @return {string} The converted tag key value that should account for its obsidian formats.
+ */
+export function convertTagValueToStringOrStringArray(value: string | string[]): string[] {
+  if (typeof value === 'string') {
+    if (value.includes(',')) {
+      return value.split(', ');
+    }
+
+    return value.split(' ');
+  }
+
+  return value;
+}
+
+/**
+ * Converts the alias over to the appropriate array items for formatting taking into account obsidian formats.
+ * @param {string | string[]} value The value of the aliases key that may need to be split into the appropriate parts.
+ * @return {string} The alias value converted to the appropriate array items for formatting.
+ */
+export function convertAliasValueToStringOrStringArray(value: string | string[]): string[] {
+  if (typeof value === 'string') {
+    return value.split(', ');
+  }
+
+  return value;
+}


Test patch:
diff --git a/__tests__/format-yaml-arrays.test.ts b/__tests__/format-yaml-arrays.test.ts
new file mode 100644
index 0000000..0ee68e2
--- /dev/null
+++ b/__tests__/format-yaml-arrays.test.ts
@@ -0,0 +1,772 @@
+import FormatYamlArray from '../src/rules/format-yaml-arrays';
+import dedent from 'ts-dedent';
+import {ruleTest} from './common';
+
+ruleTest({
+  RuleBuilderClass: FormatYamlArray,
+  testCases: [
+    // tags
+    {
+      testName: 'Convert tags from single-line with spaces to multi-line array',
+      before: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags:
+          - tag1
+          - tag2
+          - tag3
+          - tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'multi-line',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line with spaces to single-line array',
+      before: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: [tag1, tag2, tag3, tag4]
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single-line',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line with spaces to single-line with commas',
+      before: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: tag1, tag2, tag3, tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string comma delimited',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line with spaces to single-line array which is space delimited',
+      before: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: [tag1 tag2 tag3 tag4]
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single-line space delimited',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line with spaces to single-line with spaces',
+      before: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string space delimited',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line array with spaces to single-line with spaces',
+      before: dedent`
+        ---
+        tags: [tag1 tag2 tag3 tag4]
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string space delimited',
+      },
+    },
+    {
+      testName: 'Convert tags from multi-line array with spaces to single string when 1 element is present',
+      before: dedent`
+        ---
+        tags:
+          - tag1
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: tag1
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string to single-line',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line with commas to single-line array when multiple elements present and option is single string to single-line',
+      before: dedent`
+        ---
+        tags: tag1, tag2, tag3, tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: [tag1, tag2, tag3, tag4]
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string to single-line',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line array with spaces to multi-line array when multiple elements present and option is single string to multi-line',
+      before: dedent`
+        ---
+        tags: tag1 tag2 tag3 tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags:
+          - tag1
+          - tag2
+          - tag3
+          - tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string to multi-line',
+      },
+    },
+    {
+      testName: 'Convert tags from single-line string to single-line string when there is only 1 element and the style is single string to multi-line',
+      before: dedent`
+        ---
+        tags: tag1, tag2, tag3, tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags:
+          - tag1
+          - tag2
+          - tag3
+          - tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string to multi-line',
+      },
+    },
+    {
+      testName: 'Formatting yaml tags does nothing when disabled',
+      before: dedent`
+        ---
+        tags: tag1, tag2, tag3, tag4
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: tag1, tag2, tag3, tag4
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string to multi-line',
+        formatTagKey: false,
+      },
+    },
+
+    // aliases
+    {
+      testName: 'Convert aliases from single-line to multi-line array',
+      before: dedent`
+        ---
+        aliases: [title1, title2, title3]
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases:
+          - title1
+          - title2
+          - title3
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'multi-line',
+      },
+    },
+    {
+      testName: 'Convert aliases from multi-line to single string which is comma delimited',
+      before: dedent`
+        ---
+        aliases:
+          - title1
+          - title2
+          - title3
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: title1, title2, title3
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single string comma delimited',
+      },
+    },
+    {
+      testName: 'Convert multi-line to single string when there is 1 element and the style is single string to single-line',
+      before: dedent`
+        ---
+        aliases:
+          - title
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: title
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single string to single-line',
+      },
+    },
+    {
+      testName: 'Convert multi-line to single string when there is 1 element and the style is single string to multi-line',
+      before: dedent`
+        ---
+        aliases:
+          - title
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: title
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single string to multi-line',
+      },
+    },
+    {
+      testName: 'Convert single-line to multi-line string when there is more than 1 element and the style is single string to multi-line',
+      before: dedent`
+        ---
+        aliases: [title, other title]
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases:
+          - title
+          - other title
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single string to multi-line',
+      },
+    },
+    {
+      testName: 'Convert multi-line to single-line when there is more than 1 element and the style is single string to single-line',
+      before: dedent`
+        ---
+        aliases:
+          - title
+          - other title
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: [title, other title]
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single string to single-line',
+      },
+    },
+    {
+      testName: 'Formatting aliases does nothing when disabled',
+      before: dedent`
+        ---
+        aliases:
+          - title
+          - other title
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases:
+          - title
+          - other title
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single string to single-line',
+        formatAliasKey: false,
+      },
+    },
+
+    // default array style
+    {
+      testName: 'Convert multi-line to single-line for regular yaml arrays',
+      before: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'single-line',
+      },
+    },
+    {
+      testName: 'Convert single-line to multi-line for regular yaml arrays',
+      before: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      after: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+      },
+    },
+    {
+      testName: 'Regular yaml array formatting does nothing when disabled',
+      before: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+        formatArrayKeys: false,
+      },
+    },
+    {
+      testName: 'Regular yaml array formatting does nothing when key is present in list for forcing to be single-line',
+      before: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+        forceSingleLineArrayStyle: ['key'],
+      },
+    },
+    {
+      testName: 'Regular yaml array formatting does nothing when key is present in list for forcing to be multi-line',
+      before: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      after: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'single-line',
+        forceMultiLineArrayStyle: ['key'],
+      },
+    },
+    {
+      testName: 'Regular yaml array formatting does nothing to tags and aliases',
+      before: dedent`
+        ---
+        aliases: [title1, other title]
+        tags: [tag1, tag2, tag3]
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: [title1, other title]
+        tags: [tag1, tag2, tag3]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+      },
+    },
+
+    // force single-line
+    {
+      testName: 'Forcing single-line on a multi-line array results in a single-line array',
+      before: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+        forceSingleLineArrayStyle: ['key'],
+      },
+    },
+    {
+      testName: 'Forcing single-line on a key that is not present just ignores the value',
+      before: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      after: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+        forceSingleLineArrayStyle: ['key1'],
+      },
+    },
+    {
+      testName: 'Forcing single-line on a value that is a single string will result in a single-line',
+      before: dedent`
+        ---
+        key: text here
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [text here]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'multi-line',
+        forceSingleLineArrayStyle: ['key'],
+      },
+    },
+
+    // force multi-line
+    {
+      testName: 'Forcing multi-line on a single-line array results in a multi-line array',
+      before: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      after: dedent`
+        ---
+        key:
+          - val1
+          - other val
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'single-line',
+        forceMultiLineArrayStyle: ['key'],
+      },
+    },
+    {
+      testName: 'Forcing multi-line on a key that does not exist results in it being ignored',
+      before: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'single-line',
+        forceMultiLineArrayStyle: ['key1'],
+      },
+    },
+    {
+      testName: 'Forcing multi-line on a key that does not exist results in it being ignored',
+      before: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      after: dedent`
+        ---
+        key: [val1, other val]
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'single-line',
+        forceMultiLineArrayStyle: ['key1'],
+      },
+    },
+    {
+      testName: 'Forcing multi-line on a key that has a single string will result in a multi-line',
+      before: dedent`
+        ---
+        key: here is some text
+        ---
+      `,
+      after: dedent`
+        ---
+        key:
+          - here is some text
+        ---
+      `,
+      options: {
+        defaultArrayStyle: 'single-line',
+        forceMultiLineArrayStyle: ['key'],
+      },
+    },
+
+    // edge cases
+    {
+      testName: 'Forcing multi-line on a key that has no value will result in an empty multi-line array',
+      before: dedent`
+        ---
+        key: 
+        ---
+      `,
+      after: dedent`
+        ---
+        key:
+          - 
+        ---
+      `,
+      options: {
+        forceMultiLineArrayStyle: ['key'],
+      },
+    },
+    {
+      testName: 'Forcing single-line on a key that has no value will result in an empty single-line array',
+      before: dedent`
+        ---
+        key: 
+        ---
+      `,
+      after: dedent`
+        ---
+        key: []
+        ---
+      `,
+      options: {
+        forceSingleLineArrayStyle: ['key'],
+      },
+    },
+    {
+      testName: 'Trying to format tags to a single string when it is has an empty single-line will result in an empty single string',
+      before: dedent`
+        ---
+        tags: []
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: 
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string to single-line',
+      },
+    },
+    {
+      testName: 'Trying to format tags to a multi-line when it is has an empty single-line will result in an empty multi-line',
+      before: dedent`
+        ---
+        tags: []
+        ---
+      `,
+      after: dedent`
+        ---
+        tags:
+          - 
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'multi-line',
+      },
+    },
+    {
+      testName: 'Trying to format tags to a single string separated by commas when it is has an empty single-line will result in an empty string',
+      before: dedent`
+        ---
+        tags: []
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: 
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string comma delimited',
+      },
+    },
+    {
+      testName: 'Trying to format tags to a single string separated by spaces when it is has an empty single-line will result in an empty string',
+      before: dedent`
+        ---
+        tags: []
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: 
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single string space delimited',
+      },
+    },
+    {
+      testName: 'Trying to format tags to a single-line separated by spaces when it is has an empty single-line will result in an empty single-line',
+      before: dedent`
+        ---
+        tags: []
+        ---
+      `,
+      after: dedent`
+        ---
+        tags: []
+        ---
+      `,
+      options: {
+        tagArrayStyle: 'single-line space delimited',
+      },
+    },
+    {
+      testName: 'Trying to format aliases to a single-line when it is has an empty string will result in an empty single-line',
+      before: dedent`
+        ---
+        aliases: 
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: []
+        ---
+      `,
+      options: {
+        aliasArrayStyle: 'single-line',
+      },
+    },
+    {
+      testName: 'Un-indented array items are associated with array',
+      before: dedent`
+        ---
+        aliases:
+        - title 1
+        - title 2
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: [title 1, title 2]
+        ---
+      `,
+    },
+    {
+      testName: 'An empty multi-line array does not get modified when linted and it is supposed to be a multi-line array',
+      before: dedent`
+        ---
+        speakers:
+          - 
+        ---
+      `,
+      after: dedent`
+        ---
+        speakers:
+          - 
+        ---
+      `,
+      options: {
+        forceMultiLineArrayStyle: ['speakers'],
+      },
+    },
+    {
+      testName: 'A multi-line array with mixed empty and non-empty values should have empty values removed',
+      before: dedent`
+        ---
+        speakers:
+          - 
+          - speaker1
+          - 
+        ---
+      `,
+      after: dedent`
+        ---
+        speakers:
+          - speaker1
+        ---
+      `,
+      options: {
+        forceMultiLineArrayStyle: ['speakers'],
+      },
+    },
+    {
+      testName: 'A single-line array with an empty value at the end should have the empty value removed',
+      before: dedent`
+        ---
+        speakers: [speaker1, ]
+        ---
+      `,
+      after: dedent`
+        ---
+        speakers: [speaker1]
+        ---
+      `,
+    },
+  ],
+});