Repository: platers/obsidian-linter (ts)

Issue:
Colon in H1 invalidates YAML
I used some titles with colons (e.g., `#V02: A title`). 
The automatic insert in the yaml title as `title: V02: A title` results in an invalid yaml (reason: double colon).

Suggestion: Some characters may be filtered before inserting them in the yaml.

Edit: On a second thought, the titles could just be inserted in quotation marks. ;)

Pull request description:
Escape title to be valid YAML
Fixes #185

Patch:
diff --git a/src/rules.ts b/src/rules.ts
index 4eeda54..bdb1733 100644
--- a/src/rules.ts
+++ b/src/rules.ts
@@ -10,6 +10,7 @@ import {
   loadYAML,
   moveFootnotesToEnd,
   yamlRegex,
+  escapeYamlString,
 } from './utils';
 import {
   Option,
@@ -921,6 +922,8 @@ export const rules: Rule[] = [
         });
         title = title || options['metadata: file name'];
 
+        title = escapeYamlString(title);
+
         return formatYAML(text, (text) => {
           const title_match_str = `\n${options['Title Key']}.*\n`;
           const title_match = new RegExp(title_match_str);
diff --git a/src/utils.ts b/src/utils.ts
index b900c60..a04a486 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,7 +1,7 @@
 import {remark} from 'remark';
 import {visit} from 'unist-util-visit';
 import type {Position} from 'unist';
-import {load} from 'js-yaml';
+import {load, dump} from 'js-yaml';
 import remarkGfm from 'remark-gfm';
 
 // Useful regexes
@@ -192,3 +192,7 @@ export function loadYAML(yaml_text: string): any {
   }
   return parsed_yaml;
 }
+
+export function escapeYamlString(str: string): string {
+  return dump(str).slice(0, -1);
+}


Test patch:
diff --git a/src/test.ts b/src/test.ts
index c9b00cb..d6e8994 100644
--- a/src/test.ts
+++ b/src/test.ts
@@ -597,3 +597,65 @@ describe('Disabled rules parsing', () => {
     expect(getDisabledRules(text)).toEqual([]);
   });
 });
+
+describe('YAML Title', () => {
+  it('Keeps unescaped title if possible', () => {
+    const before = dedent`
+    # Hello world
+    `;
+
+    const after = dedent`
+    ---
+    title: Hello world
+    ---
+    # Hello world
+    `;
+
+    expect(rulesDict['yaml-title'].apply(before, {'Title Key': 'title'})).toBe(after);
+  });
+
+  it('Escapes title if it contains colon', () => {
+    const before = dedent`
+    # Hello: world
+    `;
+
+    const after = dedent`
+    ---
+    title: 'Hello: world'
+    ---
+    # Hello: world
+    `;
+
+    expect(rulesDict['yaml-title'].apply(before, {'Title Key': 'title'})).toBe(after);
+  });
+
+  it('Escapes title if it starts with single quote', () => {
+    const before = dedent`
+    # 'Hello world
+    `;
+
+    const after = dedent`
+    ---
+    title: '''Hello world'
+    ---
+    # 'Hello world
+    `;
+
+    expect(rulesDict['yaml-title'].apply(before, {'Title Key': 'title'})).toBe(after);
+  });
+
+  it('Escapes title if it starts with double quote', () => {
+    const before = dedent`
+    # "Hello world
+    `;
+
+    const after = dedent`
+    ---
+    title: '"Hello world'
+    ---
+    # "Hello world
+    `;
+
+    expect(rulesDict['yaml-title'].apply(before, {'Title Key': 'title'})).toBe(after);
+  });
+});