Repository: platers/obsidian-linter (ts)

Issue:
Bug: YAML Key Sort option removes multi-line content after literal operator `|`
- [x] I have verified that I am on the latest version of the Linter

## Describe the Bug

When the YAML Key Sort option `Sorts the YAML keys based on the order and priority specified.` and a `YAML Sort Order for Other Keys` order  is enabled, nested content after a literal operator `|` is removed.

This is a problem for me because I'm using [Obsidian Custom Sort](https://github.com/SebastianMC/obsidian-custom-sort), which recommends syntax with a `sorting-spec` key followed by a literal operator.

## How to Reproduce

1. Enable `Settings > Linter > Sorts the YAML keys based on the order and priority specified.`
2. Set `YAML Sort Order for Other Keys` to `Ascending Alphabetical` or `Descending Alphabetical`.
3. Add front matter with a literal operator and nested content.
4. Run the linter.
5. The nested lines after the `|` will be removed.

### Example

```yaml
---
sorting-spec: |
  order-desc: a-z
---
```

`order-desc: a-z` will be removed.

## Expected Behavior

Nested content after a literal operator should not be removed.

## Device

- [X] Desktop
- [ ] Mobile

Pull request description:
Swap to from `js-yaml` to `yaml` for Parsing YAML
Fixes #1082 #912 #992 #660 #632 #649 #1258

The following are a list of tickets that are potentially fixable with the merger of this change, but logic will need to change in order to properly handle these scenarios:

Relates to #715 #1217 #617

The main reason for making this change is getting closer to having a round trip parser for the Linter to use on the YAML. This change helps with supporting more YAML syntax as well as having a less buggy experience for end users. It should result in fewer instances where things get broken.

It should do its best to keep things as they were, but there is no guarantee that that will be the case for all formats. However it will do its best given that it is using the CST instead of the AST for serializing to YAML.



Patch:
diff --git a/package-lock.json b/package-lock.json
index f6f277d..f4a0f4b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,18 +1,17 @@
 {
   "name": "obsidian-linter",
-  "version": "1.27.0-rc-1",
+  "version": "1.27.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "obsidian-linter",
-      "version": "1.27.0-rc-1",
+      "version": "1.27.1",
       "license": "MIT",
       "dependencies": {
         "@popperjs/core": "^2.11.6",
         "async-lock": "^1.4.1",
         "diff-match-patch": "^1.0.5",
-        "js-yaml": "^4.1.0",
         "loglevel": "^1.9.1",
         "mdast-util-from-markdown": "^2.0.0",
         "mdast-util-frontmatter": "^2.0.1",
@@ -26,7 +25,8 @@
         "moment-parseformat": "^4.0.0",
         "quick-lru": "^7.0.0",
         "ts-dedent": "^2.2.0",
-        "unist-util-visit": "^5.0.0"
+        "unist-util-visit": "^5.0.0",
+        "yaml": "^2.7.0"
       },
       "devDependencies": {
         "@babel/core": "^7.24.0",
@@ -41,7 +41,6 @@
         "@types/diff": "^5.0.9",
         "@types/diff-match-patch": "^1.0.32",
         "@types/jest": "^29.5.12",
-        "@types/js-yaml": "^4.0.5",
         "@types/node": "^20.11.28",
         "@typescript-eslint/eslint-plugin": "^7.2.0",
         "@typescript-eslint/parser": "^7.2.0",
@@ -3408,12 +3407,6 @@
         "pretty-format": "^29.0.0"
       }
     },
-    "node_modules/@types/js-yaml": {
-      "version": "4.0.9",
-      "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
-      "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
-      "dev": true
-    },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -4306,7 +4299,8 @@
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
     },
     "node_modules/array-union": {
       "version": "2.1.0",
@@ -8500,6 +8494,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
       "dependencies": {
         "argparse": "^2.0.1"
       },
@@ -11760,10 +11755,9 @@
       "dev": true
     },
     "node_modules/yaml": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
-      "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
-      "dev": true,
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
+      "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
       "bin": {
         "yaml": "bin.mjs"
       },
@@ -14162,12 +14156,6 @@
         "pretty-format": "^29.0.0"
       }
     },
-    "@types/js-yaml": {
-      "version": "4.0.9",
-      "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
-      "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
-      "dev": true
-    },
     "@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -14769,7 +14757,8 @@
     "argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
     },
     "array-union": {
       "version": "2.1.0",
@@ -17803,6 +17792,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
       "requires": {
         "argparse": "^2.0.1"
       }
@@ -20021,10 +20011,9 @@
       "dev": true
     },
     "yaml": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
-      "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
-      "dev": true
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
+      "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="
     },
     "yargs": {
       "version": "17.7.2",
diff --git a/package.json b/package.json
index c58f726..f00c169 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
     "@types/diff": "^5.0.9",
     "@types/diff-match-patch": "^1.0.32",
     "@types/jest": "^29.5.12",
-    "@types/js-yaml": "^4.0.5",
     "@types/node": "^20.11.28",
     "@typescript-eslint/eslint-plugin": "^7.2.0",
     "@typescript-eslint/parser": "^7.2.0",
@@ -61,7 +60,6 @@
     "@popperjs/core": "^2.11.6",
     "async-lock": "^1.4.1",
     "diff-match-patch": "^1.0.5",
-    "js-yaml": "^4.1.0",
     "loglevel": "^1.9.1",
     "mdast-util-from-markdown": "^2.0.0",
     "mdast-util-frontmatter": "^2.0.1",
@@ -75,6 +73,7 @@
     "moment-parseformat": "^4.0.0",
     "quick-lru": "^7.0.0",
     "ts-dedent": "^2.2.0",
-    "unist-util-visit": "^5.0.0"
+    "unist-util-visit": "^5.0.0",
+    "yaml": "^2.7.0"
   }
 }
diff --git a/src/rules-runner.ts b/src/rules-runner.ts
index 00d3d26..910ed17 100644
--- a/src/rules-runner.ts
+++ b/src/rules-runner.ts
@@ -32,7 +32,6 @@ import {CustomAutoCorrectContent} from './ui/linter-components/auto-correct-file
 import AutoCorrectCommonMisspellings from './rules/auto-correct-common-misspellings';
 import {yamlRegex} from './utils/regex';
 import AddBlankLineAfterYAML from './rules/add-blank-line-after-yaml';
-import ConsecutiveBlankLines from './rules/consecutive-blank-lines';
 
 export type RunLinterRulesOptions = {
   oldText: string,
@@ -172,8 +171,6 @@ export class RulesRunner {
 
     [newText] = TrailingSpaces.applyIfEnabled(newText, runOptions.settings, this.disabledRules);
 
-    [newText] = ConsecutiveBlankLines.applyIfEnabled(newText, runOptions.settings, this.disabledRules);
-
     const yaml = newText.match(yamlRegex);
     if (yaml != null) {
       [newText] = AddBlankLineAfterYAML.applyIfEnabled(newText, runOptions.settings, this.disabledRules);
diff --git a/src/rules/consecutive-blank-lines.ts b/src/rules/consecutive-blank-lines.ts
index a95154e..2ff4255 100644
--- a/src/rules/consecutive-blank-lines.ts
+++ b/src/rules/consecutive-blank-lines.ts
@@ -13,7 +13,6 @@ export default class ConsecutiveBlankLines extends RuleBuilder<ConsecutiveBlankL
       descriptionKey: 'rules.consecutive-blank-lines.description',
       type: RuleType.SPACING,
       ruleIgnoreTypes: [IgnoreTypes.code, IgnoreTypes.math, IgnoreTypes.yaml, IgnoreTypes.link, IgnoreTypes.wikiLink, IgnoreTypes.tag],
-      hasSpecialExecutionOrder: true, // runs after most rules to cleanup any leftover spacing https://github.com/platers/obsidian-linter/issues/1225
     });
   }
   get OptionsClass(): new () => ConsecutiveBlankLinesOptions {
diff --git a/src/rules/yaml-key-sort.ts b/src/rules/yaml-key-sort.ts
index 345e0bf..f86b95e 100644
--- a/src/rules/yaml-key-sort.ts
+++ b/src/rules/yaml-key-sort.ts
@@ -1,7 +1,10 @@
 import {Options, RuleType} from '../rules';
 import RuleBuilder, {BooleanOptionBuilder, DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
 import dedent from 'ts-dedent';
-import {getYAMLText, getYamlSectionValue, loadYAML, removeYamlSection, setYamlSection} from '../utils/yaml';
+import {parseYAML, getYAMLText, loadYAML, setYamlSection, astToString, getEmptyDocument} from '../utils/yaml';
+import {Document} from 'yaml';
+import {YamlCSTTokens, YamlNode} from '../typings/yaml';
+import {FlowCollection} from 'yaml/dist/parse/cst';
 
 type YamlSortOrderForOtherKeys = 'None' | 'Ascending Alphabetical' | 'Descending Alphabetical';
 
@@ -40,7 +43,7 @@ export default class YamlKeySort extends RuleBuilder<YamlKeySortOptions> {
       return text;
     }
 
-    let yamlText = oldYaml;
+    const yamlText = oldYaml;
     const priorityAtStartOfYaml: boolean = options.priorityKeysAtStartOfYaml;
 
     const yamlKeys: string[] = options.yamlKeyPrioritySortOrder;
@@ -57,55 +60,54 @@ export default class YamlKeySort extends RuleBuilder<YamlKeySortOptions> {
     }
 
     const yamlObject = loadYAML(yamlText);
-    const sortKeysResult = this.getYAMLKeysSorted(yamlText, yamlKeys, yamlObject);
-    const priorityKeysSorted = sortKeysResult.sortedYamlKeyValues;
-    yamlText = sortKeysResult.remainingYaml;
+    const doc = parseYAML(yamlText);
+    const startingPriorityKeys = getEmptyDocument(doc);
+
+    let remainingKeys = this.getYAMLKeysSorted(yamlKeys, doc, startingPriorityKeys);
 
     const sortOrder = options.yamlSortOrderForOtherKeys;
     if (yamlObject == null) {
-      return this.getTextWithNewYamlFrontmatter(text, oldYaml, priorityKeysSorted, yamlText, priorityAtStartOfYaml, options.dateModifiedKey, options.currentTimeFormatted, options.yamlTimestampDateModifiedEnabled);
+      return this.getTextWithNewYamlFrontmatter(text, oldYaml, astToString(startingPriorityKeys), astToString(doc), priorityAtStartOfYaml, options.dateModifiedKey, options.currentTimeFormatted, options.yamlTimestampDateModifiedEnabled);
     }
 
-    let remainingKeys = Object.keys(yamlObject);
     let sortMethod: (previousKey: string, currentKey: string) => number;
     if (sortOrder === 'Ascending Alphabetical') {
       sortMethod = this.sortAlphabeticallyAsc;
     } else if (sortOrder === 'Descending Alphabetical') {
       sortMethod = this.sortAlphabeticallyDesc;
     } else {
-      return this.getTextWithNewYamlFrontmatter(text, oldYaml, priorityKeysSorted, yamlText, priorityAtStartOfYaml, options.dateModifiedKey, options.currentTimeFormatted, options.yamlTimestampDateModifiedEnabled);
+      return this.getTextWithNewYamlFrontmatter(text, oldYaml, astToString(startingPriorityKeys), astToString(doc), priorityAtStartOfYaml, options.dateModifiedKey, options.currentTimeFormatted, options.yamlTimestampDateModifiedEnabled);
     }
 
+    const remainingDocKeys = getEmptyDocument(doc);
     remainingKeys = remainingKeys.sort(sortMethod);
-    const remainingKeysSortResult = this.getYAMLKeysSorted(yamlText, remainingKeys, yamlObject);
+    this.getYAMLKeysSorted(remainingKeys, doc, remainingDocKeys);
 
-    return this.getTextWithNewYamlFrontmatter(text, oldYaml, priorityKeysSorted, remainingKeysSortResult.sortedYamlKeyValues, priorityAtStartOfYaml, options.dateModifiedKey, options.currentTimeFormatted, options.yamlTimestampDateModifiedEnabled);
+    return this.getTextWithNewYamlFrontmatter(text, oldYaml, astToString(startingPriorityKeys), astToString(remainingDocKeys), priorityAtStartOfYaml, options.dateModifiedKey, options.currentTimeFormatted, options.yamlTimestampDateModifiedEnabled);
   }
-  getYAMLKeysSorted(yaml: string, keys: string[], yamlObject: any): {remainingYaml: string, sortedYamlKeyValues: string} {
-    let specifiedYamlKeysSorted = '';
-    for (const key of keys) {
-      // we skip any nested elements when sorting to prevent issues where possible
-      if (!(key in yamlObject)) {
-        continue;
-      }
+  getYAMLKeysSorted(keys: string[], yamlObject: Document, newDocument: Document): string[] {
+    const initialKeys: YamlNode[] = (yamlObject.contents as YamlNode).items as YamlNode[];
+    const remainingKeys: string[] = [];
 
-      const value = getYamlSectionValue(yaml, key, false);
+    for (const key of keys) {
+      for (let i = 0; i < initialKeys.length; i++) {
+        const node = initialKeys[i];
+        if (node.key.value === key) {
+          newDocument.add(node);
+          initialKeys.splice(i, 1);
+          (newDocument.contents.srcToken as YamlCSTTokens).items.push((yamlObject.contents.srcToken as FlowCollection).items[i]);
+          (yamlObject.contents.srcToken as FlowCollection).items.splice(i, 1);
 
-      if (value !== null) {
-        if (value.includes('\n')) {
-          specifiedYamlKeysSorted += `${key}:${value}\n`;
-        } else {
-          specifiedYamlKeysSorted += `${key}: ${value}\n`;
+          break;
         }
-
-        yaml = removeYamlSection(yaml, key, false);
       }
     }
 
-    return {
-      remainingYaml: yaml,
-      sortedYamlKeyValues: specifiedYamlKeysSorted,
-    };
+    for (const node of initialKeys) {
+      remainingKeys.push(node.key.value);
+    }
+
+    return remainingKeys;
   }
   updateDateModifiedIfYamlChanged(oldYaml: string, newYaml: string, dateModifiedKey: string, currentTimeFormatted: string): string {
     if (oldYaml == newYaml) {
@@ -247,16 +249,19 @@ export default class YamlKeySort extends RuleBuilder<YamlKeySortOptions> {
           status: WIP
           date: 02/15/2022
           ---
+          Any blank line is attached to the line that follows it
         `,
         after: dedent`
           ---
           tags: computer
+          ${''}
           status: WIP
           keywords: []
           date: 02/15/2022
           type: programming
           language: Typescript
           ---
+          Any blank line is attached to the line that follows it
         `,
         options: {
           yamlKeyPrioritySortOrder: [
diff --git a/src/typings/obsidian-ex.d.ts b/src/typings/obsidian-ex.d.ts
index 46a596f..7b0ee3c 100644
--- a/src/typings/obsidian-ex.d.ts
+++ b/src/typings/obsidian-ex.d.ts
@@ -74,7 +74,7 @@ declare module 'obsidian' {
      * CodeMirror editor instance
      */
     cm?: EditorView;
-}
+  }
 }
 
 
diff --git a/src/typings/yaml.d.ts b/src/typings/yaml.d.ts
new file mode 100644
index 0000000..a1ecef1
--- /dev/null
+++ b/src/typings/yaml.d.ts
@@ -0,0 +1,35 @@
+import {CST} from 'yaml';
+import {QuoteCharacter} from '../utils/yaml';
+
+interface YamlProperties {
+  lineWidth: number,
+  quotingType: QuoteCharacter,
+  forceQuotes: boolean,
+}
+
+interface Key {
+  value?: string;
+}
+
+interface YamlCSTTokens {
+  offset?: number,
+  type: string,
+  indent?: number,
+  start?: number;
+  end?: number;
+  items: CST.CollectionItem[];
+}
+
+interface YamlNode {
+  constructor: { name: string };
+  key?: Key;
+  value?: any;
+  items?: [string, any][];
+  moved?: boolean;
+  srcTokens?: YamlCSTTokens;
+}
+
+declare module 'yaml' {
+  // for now we really just need this for use with strings, so we shall work with that;
+  export function stringify(value: string, properties?: YamlProperties): string;
+}
diff --git a/src/utils/yaml.ts b/src/utils/yaml.ts
index bf55c19..07a9514 100644
--- a/src/utils/yaml.ts
+++ b/src/utils/yaml.ts
@@ -1,8 +1,9 @@
-import {load, dump} from 'js-yaml';
 import {getTextInLanguage} from '../lang/helpers';
 import {escapeDollarSigns, yamlRegex} from './regex';
 import {isNumeric} from './strings';
-
+import {parse, parseDocument, Document, stringify, CST, YAMLMap} from 'yaml';
+import {YamlNode} from 'src/typings/yaml';
+import {FlowCollection} from 'yaml/dist/parse/cst';
 
 export const OBSIDIAN_TAG_KEY_SINGULAR = 'tag';
 export const OBSIDIAN_TAG_KEY_PLURAL = 'tags';
@@ -93,7 +94,7 @@ export function loadYAML(yaml_text: string): any {
 
   // replacing tabs at the beginning of new lines with 2 spaces fixes loading YAML that has tabs at the start of a line
   // https://github.com/platers/obsidian-linter/issues/157
-  const parsed_yaml = load(yaml_text.replace(/\n(\t)+/g, '\n  ')) as {};
+  const parsed_yaml = parse(yaml_text.replace(/\n(\t)+/g, '\n  ')) as {};
   if (parsed_yaml == null) {
     return {};
   }
@@ -101,6 +102,51 @@ export function loadYAML(yaml_text: string): any {
   return parsed_yaml;
 }
 
+export function parseYAML(yaml_text: string): Document {
+  if (yaml_text == null) {
+    return null;
+  }
+
+  // replacing tabs at the beginning of new lines with 2 spaces fixes loading YAML that has tabs at the start of a line
+  // https://github.com/platers/obsidian-linter/issues/157
+  const parsed_yaml = parseDocument(yaml_text.replace(/\n(\t)+/g, '\n  '), {keepSourceTokens: true});
+  if (parsed_yaml == null) {
+    return null;
+  }
+
+  return parsed_yaml;
+}
+
+export function getEmptyDocument(doc: Document): Document {
+  const newDocument = new Document(doc.options);
+  newDocument.contents = new YAMLMap();
+
+  const originalToken = doc.contents.srcToken as FlowCollection;
+  newDocument.contents.srcToken = {
+    offset: originalToken.offset,
+    type: originalToken.type,
+    indent: originalToken.indent,
+    start: originalToken.start,
+    end: originalToken.end,
+    items: [] as CST.CollectionItem[],
+  };
+
+  return newDocument;
+}
+
+export function astToString(ast: Document): string {
+  if (!ast || !ast.contents) {
+    return '';
+  }
+
+  const items = (ast.contents as YamlNode).items as YamlNode[];
+  if (!items || items.length == 0) {
+    return '';
+  }
+
+  return CST.stringify(ast.contents.srcToken);
+}
+
 export enum TagSpecificArrayFormats {
   SingleStringSpaceDelimited = 'single string space delimited',
   SingleLineSpaceDelimited = 'single-line space delimited',
@@ -382,7 +428,7 @@ export function escapeStringIfNecessaryAndPossible(value: string, defaultEscapeC
   }
 
   try {
-    const unescaped = load(basicEscape) as string;
+    const unescaped = parse(basicEscape) as string;
     if (unescaped === value) {
       return basicEscape;
     }
@@ -390,13 +436,13 @@ export function escapeStringIfNecessaryAndPossible(value: string, defaultEscapeC
     // invalid YAML
   }
 
-  const escapeWithDefaultCharacter = dump(value, {
+  const escapeWithDefaultCharacter = stringify(value, {
     lineWidth: -1,
     quotingType: defaultEscapeCharacter,
     forceQuotes: forceEscape,
   }).slice(0, -1);
 
-  const escapeWithOtherCharacter = dump(value, {
+  const escapeWithOtherCharacter = stringify(value, {
     lineWidth: -1,
     quotingType: defaultEscapeCharacter == '"' ? '\'' : '"',
     forceQuotes: forceEscape,


Test patch:
diff --git a/__tests__/yaml-key-sort.test.ts b/__tests__/yaml-key-sort.test.ts
index 7a75001..28796f3 100644
--- a/__tests__/yaml-key-sort.test.ts
+++ b/__tests__/yaml-key-sort.test.ts
@@ -199,5 +199,165 @@ ruleTest({
         ],
       },
     },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/1082
+      testName: 'Make sure that literal operator `|` is handled correctly',
+      before: dedent`
+        ---
+        sorting-spec: |
+          order-desc: a-z
+        first: alphabetical
+        ---
+      `,
+      after: dedent`
+        ---
+        first: alphabetical
+        sorting-spec: |
+          order-desc: a-z
+        ---
+      `,
+      options: {
+        yamlKeyPrioritySortOrder: [
+          'first',
+          'sorting-spec:',
+        ],
+      },
+    },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/912
+      testName: 'Make sure that lots of nesting does not get broken',
+      before: dedent`
+        ---
+        AAA:
+          - BBB: x
+            CCC: x
+          - DDD: x
+            EEE: x
+            FFF:
+              - GGG: x
+              - HHH
+            III: x
+        FFF:
+          -${' '}
+        ---
+      `,
+      after: dedent`
+        ---
+        AAA:
+          - BBB: x
+            CCC: x
+          - DDD: x
+            EEE: x
+            FFF:
+              - GGG: x
+              - HHH
+            III: x
+        FFF:
+          -${' '}
+        ---
+      `, // note that this does look wonky, but it works the same in Obsidian, so I am going to consider this fine for now
+      // see https://github.com/eemeli/yaml/issues/590 for more information on this
+      options: {
+        yamlSortOrderForOtherKeys: 'Ascending Alphabetical',
+      },
+    },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/660
+      testName: 'Make sure that comments are not removed when sorting',
+      before: dedent`
+        ---
+        created: 2023-02-18T10:53:01+00:00
+        # Dont remove me
+        disabled rules: [capitalize-headings]
+        modified: 2023-03-20T14:53:42+00:00
+        ---
+      `,
+      after: dedent`
+        ---
+        created: 2023-02-18T10:53:01+00:00
+        # Dont remove me
+        disabled rules: [capitalize-headings]
+        modified: 2023-03-20T14:53:42+00:00
+        ---
+      `,
+      options: {
+        yamlSortOrderForOtherKeys: 'Ascending Alphabetical',
+      },
+    },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/660
+      testName: 'Make sure that scalars are not removed when sorting',
+      before: dedent`
+        ---
+        name: John
+        husband: Tim
+        biography: |
+          A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap.
+        ---
+      `,
+      after: dedent`
+        ---
+        biography: |
+          A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap. A very nice chap.
+        husband: Tim
+        name: John
+        ---
+      `,
+      options: {
+        yamlSortOrderForOtherKeys: 'Ascending Alphabetical',
+      },
+    },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/1258
+      testName: 'Make sure that folded block scalars are not broken up when sorting',
+      before: dedent`
+        ---
+        aliases: []
+        tags:
+          - media/podcast/episode
+        description: >-
+          Follow our ad-free Rain Playlist here:
+        image: 'https://i.scdn.co/image/ab6765630000ba8a8d796ac81fdad1d895dd28a1'
+        links:
+          - https://open.spotify.com/episode/1LSnoZVPWB8ynOCMCwKw5S
+        showName: ASMR Rain Recordings
+        created: 11-05-2024 21:09
+        updated: 13-01-2025 08:16
+        ---
+      `,
+      after: dedent`
+        ---
+        aliases: []
+        created: 11-05-2024 21:09
+        description: >-
+          Follow our ad-free Rain Playlist here:
+        image: 'https://i.scdn.co/image/ab6765630000ba8a8d796ac81fdad1d895dd28a1'
+        links:
+          - https://open.spotify.com/episode/1LSnoZVPWB8ynOCMCwKw5S
+        showName: ASMR Rain Recordings
+        tags:
+          - media/podcast/episode
+        updated: 13-01-2025 08:16
+        ---
+      `,
+      options: {
+        yamlSortOrderForOtherKeys: 'Ascending Alphabetical',
+      },
+    },
+    { // accounts for https://github.com/platers/obsidian-linter/issues/660
+      testName: 'Make sure that comments on an array do not cause issues when sorting',
+      before: dedent`
+        ---
+        Ingredients: # [\`<free text for ingredient>\`,\`<amount>\`, \`<unit>\`, \`<form>\`, \`<note>\`]
+          - [Watermelon, 80 g, cubed, note]
+          - [sugar, 20 g]
+        ---
+      `,
+      after: dedent`
+        ---
+        Ingredients: # [\`<free text for ingredient>\`,\`<amount>\`, \`<unit>\`, \`<form>\`, \`<note>\`]
+          - [Watermelon, 80 g, cubed, note]
+          - [sugar, 20 g]
+        ---
+      `,
+      options: {
+        yamlSortOrderForOtherKeys: 'Ascending Alphabetical',
+      },
+    },
   ],
 });