Repository: rwjblue/ember-cli-template-lint (js)

Issue:
Enforce indentation for multiline blocks.
This would likely fall into the `block-indentation` rule, but the basic idea is that for HTML elements that span multiple lines you want to ensure they start and stop at the same location.

```hbs
<!-- bad -->

<div>
  <p>SomeStuff Here</p></div>
```

```hbs
<!-- good -->

<div>
  <p>SomeStuff Here</p>
</div>

<div><p>SomeStuff Here</p></div>
```

Pull request description:
Implement HTML element block indentation requirment.
When an HTML element node is multi-line, the `block-indentation` rule now requires that it start and end at the same indentation level.

Example:

```
<div>
  <p>Stuff goes here</p></div>
```

Would result in the following message:

```
Incorrect indentation for `div` beginning at ('layout.hbs'@ L1:C0).
Expected `</div>` ending at ('layout.hbs'@ L2:C30)to be at an indentation
of 0 but was found at 24.
```

Fixes #30.

Patch:
diff --git a/README.md b/README.md
index 2c8df2a..a8bb135 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ This rule forbids the following:
 
 Good indentation is crucial for long term maintenance of templates. For example, having blocks misaligned is a common cause of logic errors...
 
-This rule forbids the following:
+This rule forbids the following examples:
 
 ``` hbs
   {{#each foo as |bar}}
@@ -74,6 +74,11 @@ This rule forbids the following:
     {{/each}}
 ```
 
+``` html
+<div>
+  <p>{{t 'Stuff here!'}}</p></div>
+```
+
 #### triple-curlies
 
 Usage of triple curly braces to allow raw HTML to be injected into the DOM is large vector for exploits of your application (especially when the raw HTML is user controllable ). Instead of using `{{{foo}}}`, you should use appropriate helpers or computed properties that return a `SafeString` (via `Ember.String.htmlSafe` generally) and ensure that user supplied data is properly escaped.
diff --git a/ext/helpers/calculate-location-display.js b/ext/helpers/calculate-location-display.js
index 77a5669..77901e4 100644
--- a/ext/helpers/calculate-location-display.js
+++ b/ext/helpers/calculate-location-display.js
@@ -1,11 +1,10 @@
 'use strict';
 
 // copied from emberjs/ember.js packages/ember-template-compiler/lib/system/calculate-location-display.js
-module.exports = function calculateLocationDisplay(moduleName, _loc) {
-  var loc = _loc || {};
-  var start = loc.start || {};
-  var column = start.column;
-  var line = start.line;
+module.exports = function calculateLocationDisplay(moduleName, _locPortion) {
+  var locPortion = _locPortion || {};
+  var column = locPortion.column;
+  var line = locPortion.line;
   var moduleInfo = '';
   if (moduleName) {
     moduleInfo +=  "'" + moduleName + "'";
diff --git a/ext/plugins/lint-bare-strings.js b/ext/plugins/lint-bare-strings.js
index fe121ba..c2416db 100644
--- a/ext/plugins/lint-bare-strings.js
+++ b/ext/plugins/lint-bare-strings.js
@@ -16,7 +16,7 @@ module.exports = function(addonContext) {
   var LogStaticStrings = buildPlugin(addonContext, 'bare-strings');
 
   LogStaticStrings.prototype.process = function(node) {
-    var locationDisplay = calculateLocationDisplay(this.options.moduleName, node.loc);
+    var locationDisplay = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.start);
     var warning = 'Non-translated string used ' + locationDisplay + ' `' + node.chars + '`';
 
     this.log(warning);
diff --git a/ext/plugins/lint-block-indentation.js b/ext/plugins/lint-block-indentation.js
index 39e4a73..91d1973 100644
--- a/ext/plugins/lint-block-indentation.js
+++ b/ext/plugins/lint-block-indentation.js
@@ -17,17 +17,51 @@ module.exports = function(addonContext) {
   var BlockIndentation = buildPlugin(addonContext, 'block-indentation');
 
   BlockIndentation.prototype.detect = function(node) {
-    return (node.type === 'BlockStatement');
+    return node.type === 'BlockStatement' || node.type === 'ElementNode';
   };
 
   BlockIndentation.prototype.process = function(node) {
+    this['process' + node.type](node);
+  };
+
+  BlockIndentation.prototype.buildWarning = function(name, locationInfo) {
+    return 'Incorrect `' + name + '` block indention at beginning at ' + locationInfo;
+  };
+
+  BlockIndentation.prototype.processElementNode = function(node) {
+    // HTML elements that start and end on the same line are fine
+    if (node.loc.start.line === node.loc.end.line) {
+      return;
+    }
+
+    var startColumn = node.loc.start.column;
+    var endColumn   = node.loc.end.column;
+
+    var correctedEndColumn = endColumn - node.tag.length - 3;
+    if(correctedEndColumn !== startColumn) {
+      var startLocation = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.start);
+      var endLocation = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.end);
+
+      var warning = 'Incorrect indentation for `' + node.tag + '` beginning at ' + startLocation +
+            '. Expected `</' + node.tag + '>` ending at ' + endLocation + 'to be at an indentation of ' + startColumn + ' but ' +
+            'was found at ' + correctedEndColumn + '.';
+      this.log(warning);
+    }
+  };
+
+  BlockIndentation.prototype.processBlockStatement = function(node) {
     var startColumn = node.loc.start.column;
     var endColumn   = node.loc.end.column;
 
     var correctedEndColumn = endColumn - node.path.original.length - 5;
     if(correctedEndColumn !== startColumn) {
-      var location = calculateLocationDisplay(this.options.moduleName, node.loc);
-      var warning  = 'Incorrect `' + node.path.original + '` block indention at beginning at ' + location;
+      var startLocation = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.start);
+      var endLocation = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.end);
+
+      var warning = 'Incorrect indentation for `' + node.path.original + '` beginning at ' + startLocation +
+            '. Expected `{{/' + node.path.original + '}}` ending at ' + endLocation + 'to be at an indentation of ' + startColumn + ' but ' +
+            'was found at ' + correctedEndColumn + '.';
+
       this.log(warning);
     }
   };
diff --git a/ext/plugins/lint-triple-curlies.js b/ext/plugins/lint-triple-curlies.js
index 2f90a5d..876afb2 100644
--- a/ext/plugins/lint-triple-curlies.js
+++ b/ext/plugins/lint-triple-curlies.js
@@ -11,7 +11,7 @@ module.exports = function(addonContext) {
   };
 
   LogTripleCurlies.prototype.process = function(node) {
-    var location = calculateLocationDisplay(this.options.moduleName, node.loc);
+    var location = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.start);
 
     this.log('Usage of triple curly brackets is unsafe `{{{' + node.path.original + '}}}` at ' + location);
   };


Test patch:
diff --git a/node-tests/unit/plugins/lint-block-indentation-test.js b/node-tests/unit/plugins/lint-block-indentation-test.js
index 5cf6aeb..02207bd 100644
--- a/node-tests/unit/plugins/lint-block-indentation-test.js
+++ b/node-tests/unit/plugins/lint-block-indentation-test.js
@@ -6,14 +6,28 @@ generateRuleTests({
   name: 'block-indentation',
 
   good: [
-    '\n  {{#each cats as |dog|}}\n  {{/each}}'
+    '\n  {{#each cats as |dog|}}\n  {{/each}}',
+    '<div><p>Stuff</p></div>',
+    '<div>\n  <p>Stuff Here</p>\n</div>'
   ],
 
   bad: [
     {
-      template: '\n  {{#each cats as |dog|}}  {{/each}}',
+      // start and end must be the same indentation
+      template: '\n  {{#each cats as |dog|}}\n        {{/each}}',
 
-      message: "Incorrect `each` block indention at beginning at ('layout.hbs'@ L2:C2)"
+      message: "Incorrect indentation for `each` beginning at ('layout.hbs'@ L2:C2). Expected `{{/each}}` ending at ('layout.hbs'@ L3:C17)to be at an indentation of 2 but was found at 8."
+    },
+    {
+      // block statements must be multiline
+      template: '{{#each cats as |dog|}}{{/each}}',
+
+      message: "Incorrect indentation for `each` beginning at ('layout.hbs'@ L1:C0). Expected `{{/each}}` ending at ('layout.hbs'@ L1:C32)to be at an indentation of 0 but was found at 23."
+    },
+    {
+      template: '<div>\n  <p>Stuff goes here</p></div>',
+
+      message: "Incorrect indentation for `div` beginning at ('layout.hbs'@ L1:C0). Expected `</div>` ending at ('layout.hbs'@ L2:C30)to be at an indentation of 0 but was found at 24."
     }
   ]
 });