Repository: rwjblue/ember-cli-template-lint
Pull request #82 (2016-04-16 20:13:50)

Added lint-self-closing-void-elements rule
related #77

tell me if there is anything to fix

rwjblue: * Can you link to the spec listing of void tags above the VOID_TAGS constant?
* Can you link to some reasoning for the rule (preferably spec language or something)? I just know I'll be asked "what is wrong with <br/>?!?" And it would be nice to just have a handy link in the README...
oriSomething: before i'm adding to the code:
- [the specs of void elements](https://www.w3.org/TR/html-markup/syntax.html#void-element)
- in the spec it's written it's an optionally. i can't find a specific link why it's a bad thing (although i remember they made this options to better compatible with XHTML). the reason i think it's bad because: 1) it's not XML (or variant SVG / XHTML); 2) it's like adding a extract space `<hr    >` it's just redundant and unneeded but it is optionally


rwjblue: Kk, sounds good. 
oriSomething: i've updated to readme to something clearer i hope
olleolleolle: Cool rule!

Spelling note: The HTML5 spec spells it `self-closing`, with a hyphen. Example: https://www.w3.org/TR/html/syntax.html#elements-0
rwjblue: Great catch! 

@oriSomething - Can you update that? Sorry for letting this linger, it's ready to land once the verbiage is tweaked...

Patch:
diff --git a/README.md b/README.md
index 05d27a4..c306275 100644
--- a/README.md
+++ b/README.md
@@ -212,6 +212,32 @@ Instead, you should write the template as:
 
 More information is available at the [Deprecation Guide](http://emberjs.com/deprecations/v1.x/#toc_code-in-code-syntax-for-code-each-code).
 
+#### lint-self-closing-void-elements
+
+HTML has no self-closing tags. The HTML 5 parser will ignore self-closing tag in
+the case of [`void elements`](https://www.w3.org/TR/html-markup/syntax.html#void-element)
+(tags that shouldn't have a `closing tag`). Although the parser will ignore it's
+unnecessary and can lead to confusing with SVG/XML code.
+
+This rule forbids the following:
+
+```hbs
+<img src="http://emberjs.com/images/ember-logo.svg" alt="ember" />
+<hr/>
+```
+
+Instead, you should write the template as:
+
+```hbs
+<img src="http://emberjs.com/images/ember-logo.svg" alt="ember">
+<hr>
+```
+
+The following values are valid configuration:
+
+  * boolean -- `true` for enabled / `false` for disabled
+
+
 ## Contributing
 
 A few ideas for where to take this in the future:
diff --git a/blueprints/ember-cli-template-lint/files/.template-lintrc.js b/blueprints/ember-cli-template-lint/files/.template-lintrc.js
index 1ebb50b..1d5220c 100644
--- a/blueprints/ember-cli-template-lint/files/.template-lintrc.js
+++ b/blueprints/ember-cli-template-lint/files/.template-lintrc.js
@@ -6,6 +6,7 @@ module.exports = {
   'block-indentation': 2,
   'html-comments': true,
   'nested-interactive': true,
+  'lint-self-closing-void-elements': true,
   'triple-curlies': true,
   'deprecated-each-syntax': true
 };
diff --git a/ext/plugins/index.js b/ext/plugins/index.js
index 11ce3b0..7df3ecd 100644
--- a/ext/plugins/index.js
+++ b/ext/plugins/index.js
@@ -5,6 +5,7 @@ module.exports = {
   'block-indentation': require('./lint-block-indentation'),
   'html-comments': require('./lint-html-comments'),
   'triple-curlies': require('./lint-triple-curlies'),
+  'lint-self-closing-void-elements': require('./lint-self-closing-void-elements'),
   'nested-interactive': require('./lint-nested-interactive'),
   'deprecated-each-syntax': require('./deprecations/lint-deprecated-each-syntax')
 };
diff --git a/ext/plugins/lint-self-closing-void-elements.js b/ext/plugins/lint-self-closing-void-elements.js
new file mode 100644
index 0000000..4f701c3
--- /dev/null
+++ b/ext/plugins/lint-self-closing-void-elements.js
@@ -0,0 +1,62 @@
+'use strict';
+
+/*
+ Disallows self-closing void elements
+
+ ```
+ {{! good }}
+ <hr>
+
+ {{! bad}}
+ <hr />
+ ```
+
+ The following values are valid configuration:
+
+   * boolean -- `true` for enabled / `false` for disabled
+ */
+
+var calculateLocationDisplay = require('../helpers/calculate-location-display');
+var buildPlugin = require('./base');
+/**
+ * [Specs of Void Elements]{@link https://www.w3.org/TR/html-markup/syntax.html#void-element}
+ * @type {Object}
+ */
+var VOID_TAGS = { area: true,
+                  base: true,
+                  br: true,
+                  col: true,
+                  command: true,
+                  embed: true,
+                  hr: true,
+                  img: true,
+                  input: true,
+                  keygen: true,
+                  link: true,
+                  meta: true,
+                  param: true,
+                  source: true,
+                  track: true,
+                  wbr: true };
+
+module.exports = function(addonContext) {
+  var LogSelfClosingVoidElements = buildPlugin(addonContext, 'lint-self-closing-void-elements');
+
+  LogSelfClosingVoidElements.prototype.detect = function(node) {
+    return this.config && node.type === 'ElementNode' && VOID_TAGS[node.tag];
+  };
+
+  LogSelfClosingVoidElements.prototype.process = function(node) {
+    var source = this.sourceForNode(node).trim();
+    var sourceEndTwoCharacters = source.slice(source.length - 2);
+
+    if (sourceEndTwoCharacters === '/>') {
+      var warning = 'Self-closing void element as <' + node.tag +
+        '> is redundant ' + calculateLocationDisplay(this.options.moduleName, node.loc.start);
+
+      this.log(warning);
+    }
+  };
+
+  return LogSelfClosingVoidElements;
+};


Test patch:
diff --git a/node-tests/unit/plugins/lint-self-closing-void-elements-test.js b/node-tests/unit/plugins/lint-self-closing-void-elements-test.js
new file mode 100644
index 0000000..0c6e122
--- /dev/null
+++ b/node-tests/unit/plugins/lint-self-closing-void-elements-test.js
@@ -0,0 +1,47 @@
+'use strict';
+
+var generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'lint-self-closing-void-elements',
+
+  config: true,
+
+  good: [
+    '<area>',
+    '<base>',
+    '<br>',
+    '<col>',
+    '<command>',
+    '<embed>',
+    '<hr>',
+    '<img>',
+    '<input>',
+    '<keygen>',
+    '<link>',
+    '<meta>',
+    '<param>',
+    '<source>',
+    '<track>',
+    '<wbr>'
+  ],
+
+  bad: [
+    { template: '<area/>', message: "Self-closing void element as <area> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<base/>', message: "Self-closing void element as <base> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<br/>', message: "Self-closing void element as <br> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<col/>', message: "Self-closing void element as <col> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<command/>', message: "Self-closing void element as <command> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<embed/>', message: "Self-closing void element as <embed> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<hr/>', message: "Self-closing void element as <hr> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<img/>', message: "Self-closing void element as <img> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<input/>', message: "Self-closing void element as <input> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<keygen/>', message: "Self-closing void element as <keygen> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<link/>', message: "Self-closing void element as <link> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<meta/>', message: "Self-closing void element as <meta> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<param/>', message: "Self-closing void element as <param> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<source/>', message: "Self-closing void element as <source> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<track/>', message: "Self-closing void element as <track> is redundant (\'layout.hbs\'@ L1:C0)" },
+    { template: '<wbr/>', config: true, message: "Self-closing void element as <wbr> is redundant (\'layout.hbs\'@ L1:C0)" }
+  ]
+});