Repository: rwjblue/ember-template-lint
Pull request #247 (2017-07-17 14:22:40)

Add require option for self-closing-void-elements
#121 

rwjblue: Awesome, thank you!
rwjblue: Can you also add details to the README for this configuration?
akashdsouza: Sure
rwjblue: FYI - a recent PR landed that moves things into docs/rules/**.md files (and link to them from the README), so you'll need to update that file instead of the README. 

Patch:
diff --git a/docs/rule/self-closing-void-elements.md b/docs/rule/self-closing-void-elements.md
index 2d0abc3..7f7ca82 100644
--- a/docs/rule/self-closing-void-elements.md
+++ b/docs/rule/self-closing-void-elements.md
@@ -19,6 +19,9 @@ Instead, you should write the template as:
 <hr>
 ```
 
+There may be cases where a self-closing tag may be necessary for void elements. In such cases, a `require` string may be passed to log the missing closing tags.
+
 The following values are valid configuration:
 
   * boolean -- `true` for enabled / `false` for disabled
+  * string -- `require` to mandate the use of self closing tags
diff --git a/lib/rules/lint-self-closing-void-elements.js b/lib/rules/lint-self-closing-void-elements.js
index 23fafae..389e2c9 100644
--- a/lib/rules/lint-self-closing-void-elements.js
+++ b/lib/rules/lint-self-closing-void-elements.js
@@ -41,18 +41,43 @@ const VOID_TAGS = {
 };
 
 module.exports = class LogSelfClosingVoidElements extends Rule {
+  parseConfig(config) {
+    let configType = typeof config;
+
+    let errorMessage = 'The self-closing-void-element rule accepts one of the following values.\n ' +
+      '  * boolean - `true` to enable and `false` to disable\n' +
+      '  * `"require" - checks for closing tags in void elements\n`' +
+      '\nYou specified `' + config + '`';
+
+    switch(configType) {
+    case 'boolean':
+      return config;
+    case 'string':
+      if (config === 'require') {
+        return config;
+      } else {
+        throw new Error(errorMessage);
+      }
+    case 'undefined':
+      return true;
+    default:
+      throw new Error(errorMessage);
+    }
+  }
   visitor() {
     return {
       ElementNode(node) {
         if (VOID_TAGS[node.tag]) {
           let source = this.sourceForNode(node).trim();
           let sourceEndTwoCharacters = source.slice(source.length - 2);
+          let isSelfClosingRequired = this.config === 'require';
+          let shouldLogError = isSelfClosingRequired ? sourceEndTwoCharacters !== '/>' : sourceEndTwoCharacters === '/>';
 
-          if (sourceEndTwoCharacters === '/>') {
-            let expected = `${source.slice(0, -2)}>`;
+          if (shouldLogError) {
+            let expected = isSelfClosingRequired ? `${source.slice(0, -1)}/>` : `${source.slice(0, -2)}>`;
 
             this.log({
-              message: 'Self-closing a void element is redundant',
+              message: isSelfClosingRequired ? 'Self-closing a void element is required' : 'Self-closing a void element is redundant',
               line: node.loc.start.line,
               column: node.loc.start.column,
               source: source,


Test patch:
diff --git a/test/unit/rules/lint-self-closing-void-elements-test.js b/test/unit/rules/lint-self-closing-void-elements-test.js
index 57ac1c5..4f67075 100644
--- a/test/unit/rules/lint-self-closing-void-elements-test.js
+++ b/test/unit/rules/lint-self-closing-void-elements-test.js
@@ -23,7 +23,71 @@ generateRuleTests({
     '<param>',
     '<source>',
     '<track>',
-    '<wbr>'
+    '<wbr>',
+    {
+      config: 'require',
+      template: '<area/>'
+    },
+    {
+      config: 'require',
+      template: '<base/>'
+    },
+    {
+      config: 'require',
+      template: '<br/>'
+    },
+    {
+      config: 'require',
+      template: '<col/>'
+    },
+    {
+      config: 'require',
+      template: '<command/>'
+    },
+    {
+      config: 'require',
+      template: '<embed/>'
+    },
+    {
+      config: 'require',
+      template: '<hr/>'
+    },
+    {
+      config: 'require',
+      template: '<img/>'
+    },
+    {
+      config: 'require',
+      template: '<input/>'
+    },
+    {
+      config: 'require',
+      template: '<keygen/>'
+    },
+    {
+      config: 'require',
+      template: '<link/>'
+    },
+    {
+      config: 'require',
+      template: '<meta/>'
+    },
+    {
+      config: 'require',
+      template: '<param/>'
+    },
+    {
+      config: 'require',
+      template: '<source/>'
+    },
+    {
+      config: 'require',
+      template: '<track/>'
+    },
+    {
+      config: 'require',
+      template: '<wbr/>'
+    }
   ],
 
   bad: [
@@ -268,6 +332,262 @@ generateRuleTests({
           text: '<wbr>'
         }
       }
+    },
+    {
+      template: '<area>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<area>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<area/>'
+        }
+      }
+    },
+    {
+      template: '<base>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<base>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<base/>'
+        }
+      }
+    },
+    {
+      template: '<br>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<br>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<br/>'
+        }
+      }
+    },
+    {
+      template: '<col>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<col>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<col/>'
+        }
+      }
+    },
+    {
+      template: '<command>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<command>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<command/>'
+        }
+      }
+    },
+    {
+      template: '<embed>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<embed>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<embed/>'
+        }
+      }
+    },
+    {
+      template: '<hr>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<hr>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<hr/>'
+        }
+      }
+    },
+    {
+      template: '<img>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<img>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<img/>'
+        }
+      }
+    },
+    {
+      template: '<input>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<input>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<input/>'
+        }
+      }
+    },
+    {
+      template: '<keygen>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<keygen>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<keygen/>'
+        }
+      }
+    },
+    {
+      template: '<link>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<link>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<link/>'
+        }
+      }
+    },
+    {
+      template: '<meta>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<meta>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<meta/>'
+        }
+      }
+    },
+    {
+      template: '<param>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<param>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<param/>'
+        }
+      }
+    },
+    {
+      template: '<source>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<source>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<source/>'
+        }
+      }
+    },
+    {
+      template: '<track>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<track>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<track/>'
+        }
+      }
+    },
+    {
+      template: '<wbr>',
+      config: 'require',
+
+      result: {
+        rule: 'self-closing-void-elements',
+        message: 'Self-closing a void element is required',
+        moduleId: 'layout.hbs',
+        source: '<wbr>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<wbr/>'
+        }
+      }
     }
   ]
 });