Repository: platers/obsidian-linter
Pull request #403 (2022-09-07 03:26:42)

Move Force Yaml Escape to Yaml Escape Rule
Relates to #400 

Changes Made:
- Moved changes from `yaml-title` to `yaml-escape-characters` which is better suited to the task
- Added a new example for the rule and added the option to allow the user to force the addition of escape characters onto specified keys if not already escaped


Patch:
diff --git a/docs/rules.md b/docs/rules.md
index d7627a5..d260b33 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -18,6 +18,8 @@ Options:
 	- `'`: Use a single quote to escape if no single or double quote is present
 - Try to Escape Single Line Arrays: Tries to escape array values assuming that an array starts with "[", ends with "]", and has items that are delimited by ",".
 	- Default: `false`
+- Force Yaml Escape on Keys: Uses the Yaml escape character on the specified Yaml keys separated by a new line character if it is not already escaped. Do not use on Yaml arrays.
+	- Default: ``
 
 Example: YAML without anything to escape
 
@@ -121,6 +123,33 @@ nestedArray2: [["value: with colon in the middle"], "value with ' a single quote
 
 _Note that escaped commas in a YAML array will be treated as a separator._
 ``````
+Example: Force YAML keys to be escaped with double quotes where not already escaped with `Force Yaml Escape on Keys = ['key', 'title', 'bool']`
+
+Before:
+
+``````markdown
+---
+key: 'Already escaped value'
+title: This is a title
+bool: false
+unaffected: value
+---
+
+_Note that the force Yaml key option should not be used with arrays._
+``````
+
+After:
+
+``````markdown
+---
+key: 'Already escaped value'
+title: "This is a title"
+bool: "false"
+unaffected: value
+---
+
+_Note that the force Yaml key option should not be used with arrays._
+``````
 
 ### Format Tags in YAML
 
@@ -585,11 +614,6 @@ Inserts the title of the file into the YAML frontmatter. Gets the title from the
 Options:
 - Title Key: Which YAML key to use for title
 	- Default: `title`
-- Yaml Escape Character: Specifies what character to put around the Title Key Yaml value if it has not already been escaped
-	- Default: `None`
-	- `None`: title: Title Here
-	- `Single Quote`: title: 'Title Here'
-	- `Double Quote`: title: "Title Here"
 
 Example: Adds a header with the title from heading.
 
diff --git a/src/rules/escape-yaml-special-characters.ts b/src/rules/escape-yaml-special-characters.ts
index c0a306f..b65e939 100644
--- a/src/rules/escape-yaml-special-characters.ts
+++ b/src/rules/escape-yaml-special-characters.ts
@@ -1,13 +1,14 @@
 import {Options, RuleType} from '../rules';
-import RuleBuilder, {BooleanOptionBuilder, DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase} from './rule-builder';
+import RuleBuilder, {BooleanOptionBuilder, DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
 import dedent from 'ts-dedent';
-import {formatYAML} from '../utils/yaml';
+import {formatYAML, getYamlSectionValue, setYamlSection} from '../utils/yaml';
 
 type DefaultEscapeCharacter = '"' | '\'';
 
 class EscapeYamlSpecialCharactersOptions implements Options {
   defaultEscapeCharacter?: DefaultEscapeCharacter = '"';
   tryToEscapeSingleLineArrays?: boolean = false;
+  forceYamlEscape?: string[] = [];
 }
 
 @RuleBuilder.register
@@ -117,7 +118,22 @@ export default class EscapeYamlSpecialCharacters extends RuleBuilder<EscapeYamlS
         yamlLines[i] = escapeSubstringIfNecessary(yamlLines[i], value);
       }
 
-      return yamlLines.join('\n');
+      let newYaml = yamlLines.join('\n');
+
+      for (const yamlKeyToEscape of options.forceYamlEscape) {
+        const keyValue = getYamlSectionValue(newYaml, yamlKeyToEscape);
+
+        if (keyValue != null) {
+          // skip yaml array values or already escaped values
+          if (keyValue.includes('\n') || keyValue.startsWith(' [') || isValueEscapedAlready(keyValue)) {
+            continue;
+          }
+
+          newYaml = setYamlSection(newYaml, yamlKeyToEscape, ` ${options.defaultEscapeCharacter}${keyValue}${options.defaultEscapeCharacter}`);
+        }
+      }
+
+      return newYaml;
     });
   }
   get exampleBuilders(): ExampleBuilder<EscapeYamlSpecialCharactersOptions>[] {
@@ -214,6 +230,33 @@ export default class EscapeYamlSpecialCharacters extends RuleBuilder<EscapeYamlS
           tryToEscapeSingleLineArrays: true,
         },
       }),
+      new ExampleBuilder({
+        description: 'Force YAML keys to be escaped with double quotes where not already escaped with `Force Yaml Escape on Keys = [\'key\', \'title\', \'bool\']`',
+        before: dedent`
+          ---
+          key: 'Already escaped value'
+          title: This is a title
+          bool: false
+          unaffected: value
+          ---
+          ${''}
+          _Note that the force Yaml key option should not be used with arrays._
+        `,
+        after: dedent`
+          ---
+          key: 'Already escaped value'
+          title: "This is a title"
+          bool: "false"
+          unaffected: value
+          ---
+          ${''}
+          _Note that the force Yaml key option should not be used with arrays._
+        `,
+        options: {
+          forceYamlEscape: ['key', 'title', 'bool'],
+          defaultEscapeCharacter: '"',
+        },
+      }),
     ];
   }
   get optionBuilders(): OptionBuilderBase<EscapeYamlSpecialCharactersOptions>[] {
@@ -240,6 +283,12 @@ export default class EscapeYamlSpecialCharacters extends RuleBuilder<EscapeYamlS
         description: 'Tries to escape array values assuming that an array starts with "[", ends with "]", and has items that are delimited by ",".',
         optionsKey: 'tryToEscapeSingleLineArrays',
       }),
+      new TextAreaOptionBuilder({
+        OptionsClass: EscapeYamlSpecialCharactersOptions,
+        name: 'Force Yaml Escape on Keys',
+        description: 'Uses the Yaml escape character on the specified Yaml keys separated by a new line character if it is not already escaped. Do not use on Yaml arrays.',
+        optionsKey: 'forceYamlEscape',
+      }),
     ];
   }
   get hasSpecialExecutionOrder(): boolean {
diff --git a/src/rules/yaml-title.ts b/src/rules/yaml-title.ts
index cb2b193..7d6c970 100644
--- a/src/rules/yaml-title.ts
+++ b/src/rules/yaml-title.ts
@@ -1,19 +1,16 @@
 import {Options, RuleType} from '../rules';
-import RuleBuilder, {DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase, TextOptionBuilder} from './rule-builder';
+import RuleBuilder, {ExampleBuilder, OptionBuilderBase, TextOptionBuilder} from './rule-builder';
 import dedent from 'ts-dedent';
 import {formatYAML, initYAML, toYamlString} from '../utils/yaml';
 import {ignoreListOfTypes, IgnoreTypes} from '../utils/ignore-types';
 import {escapeDollarSigns} from '../utils/regex';
 import {insert} from '../utils/strings';
 
-type yamlTitleEscapeOptions = 'None' | 'Single Quote' | 'Double Quote';
-
 class YamlTitleOptions implements Options {
   @RuleBuilder.noSettingControl()
     fileName: string;
 
   titleKey?: string = 'title';
-  yamlEscapeCharacter?: yamlTitleEscapeOptions = 'None';
 }
 
 @RuleBuilder.register
@@ -39,26 +36,10 @@ export default class YamlTitle extends RuleBuilder<YamlTitleOptions> {
       }
       return '';
     });
-
     title = title || options.fileName;
 
     title = toYamlString(title);
 
-    const ensureSpecifiedCharacterStartsTitleIfNotAlreadyEscaped = function(title: string, characterToStartAndEndWith: string, characterToRemoveFromStartAndEnd: string) {
-      if ((title.startsWith(characterToRemoveFromStartAndEnd) && title.endsWith(characterToRemoveFromStartAndEnd)) ||
-      (title.startsWith(characterToStartAndEndWith) && title.endsWith(characterToStartAndEndWith))) {
-        return title;
-      }
-
-      return characterToStartAndEndWith + title + characterToStartAndEndWith;
-    };
-
-    if (options.yamlEscapeCharacter == 'Single Quote') {
-      title = ensureSpecifiedCharacterStartsTitleIfNotAlreadyEscaped(title, '\'', '"');
-    } else if (options.yamlEscapeCharacter == 'Double Quote') {
-      title = ensureSpecifiedCharacterStartsTitleIfNotAlreadyEscaped(title, '"', '\'');
-    }
-
     return formatYAML(text, (text) => {
       const title_match_str = `\n${options.titleKey}.*\n`;
       const title_match = new RegExp(title_match_str);
@@ -117,26 +98,6 @@ export default class YamlTitle extends RuleBuilder<YamlTitleOptions> {
         description: 'Which YAML key to use for title',
         optionsKey: 'titleKey',
       }),
-      new DropdownOptionBuilder({
-        OptionsClass: YamlTitleOptions,
-        name: 'Yaml Escape Character',
-        description: 'Specifies what character to put around the Title Key Yaml value if it has not already been escaped',
-        optionsKey: 'yamlEscapeCharacter',
-        records: [
-          {
-            value: 'None',
-            description: 'title: Title Here',
-          },
-          {
-            value: 'Single Quote',
-            description: 'title: \'Title Here\'',
-          },
-          {
-            value: 'Double Quote',
-            description: 'title: "Title Here"',
-          },
-        ],
-      }),
     ];
   }
 }


Test patch:
diff --git a/__tests__/yaml-title.test.ts b/__tests__/yaml-title.test.ts
index 57cfad5..608fc3a 100644
--- a/__tests__/yaml-title.test.ts
+++ b/__tests__/yaml-title.test.ts
@@ -65,53 +65,5 @@ ruleTest({
         # Very long title 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
       `,
     },
-    {
-      testName: 'Escaping with single quotes works',
-      before: dedent`
-        # This is a title
-      `,
-      after: dedent`
-        ---
-        title: 'This is a title'
-        ---
-        # This is a title
-      `,
-      options: {
-        fileName: 'testFile',
-        yamlEscapeCharacter: 'Single Quote',
-      },
-    },
-    {
-      testName: 'Escaping with double quotes works',
-      before: dedent`
-        # This is a title
-      `,
-      after: dedent`
-        ---
-        title: "This is a title"
-        ---
-        # This is a title
-      `,
-      options: {
-        fileName: 'testFile',
-        yamlEscapeCharacter: 'Double Quote',
-      },
-    },
-    {
-      testName: 'Escaping with double quotes does not come into affect when already escaped with single quotes',
-      before: dedent`
-        # "This is a title
-      `,
-      after: dedent`
-        ---
-        title: '"This is a title'
-        ---
-        # "This is a title
-      `,
-      options: {
-        fileName: 'testFile',
-        yamlEscapeCharacter: 'Double Quote',
-      },
-    },
   ],
 });