Repository: rwjblue/ember-cli-template-lint
Pull request #66 (2016-03-26 00:34:24)

Make the block-indentation rule aware of leading content
If the block children are inline elements, then there can be important reasons for them to appear on the same line. For example:

```javascript
<div>
  <span class="key">key</span>:<span class="value">value</span>
</div>
```

will render as

```
key: value
```

but

```javascript
<div>
  <span class="key">key</span>:
  <span class="value">value</span>
</div>
```

will render as

```
key:
value
```

The block indentation rule was previously forbidding the first version because the second ```<span>```'s column number is not two. This commit makes the indentation rule aware of previous content on the same line, whether it is an element/mustache node or text.


Patch:
diff --git a/ext/plugins/lint-block-indentation.js b/ext/plugins/lint-block-indentation.js
index 15b1244..a70a2b8 100644
--- a/ext/plugins/lint-block-indentation.js
+++ b/ext/plugins/lint-block-indentation.js
@@ -148,7 +148,7 @@ module.exports = function(addonContext) {
       var endLocation = calculateLocationDisplay(this.options.moduleName, node.loc && node.loc.end);
 
       var warning = 'Incorrect indentation for `' + displayName + '` beginning at ' + startLocation +
-            '. Expected `' + display + '` ending at ' + endLocation + 'to be at an indentation of ' + startColumn + ' but ' +
+            '. Expected `' + display + '` ending at ' + endLocation + ' to be at an indentation of ' + startColumn + ' but ' +
             'was found at ' + correctedEndColumn + '.';
       this.log(warning);
     }
@@ -173,6 +173,43 @@ module.exports = function(addonContext) {
       var child = children[i];
       if (!child.loc) { continue; }
 
+      // We might not actually be the first thing on the line. We might be
+      // preceded by another element or statement, or by some text. So walk
+      // backwards looking for something else on this line.
+      var hasLeadingContent = false;
+      for (var j = i - 1; j >= 0; j--) {
+        var sibling = children[j];
+        if (sibling.loc) {
+          // Found an element or statement. If it's on this line, then we
+          // have leading content, so set the flag and break. If it's not
+          // on this line, then we've scanned back to a previous line, so
+          // we can also break.
+          if (sibling.loc.end.line === child.loc.start.line) {
+            hasLeadingContent = true;
+          }
+          break;
+        } else {
+          var lines = sibling.chars.split(/[\r\n]/);
+          var lastLine = lines[lines.length - 1];
+          if (lastLine.trim()) {
+            // The last line in this text node has non-whitespace content, so
+            // set the flag.
+            hasLeadingContent = true;
+          }
+          if (lines.length > 1) {
+            // There are multiple lines meaning we've now scanned back to a
+            // previous line, so we can break.
+            break;
+          }
+        }
+      }
+
+      if (hasLeadingContent) {
+        // There's content before us on the same line, so we don't care about
+        // our column.
+        continue;
+      }
+
       var childStartColumn = child.loc.start.column;
       if (expectedStartColumn !== childStartColumn) {
         var isElementNode = child.type === 'ElementNode';


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 f4c7e08..03a6e93 100644
--- a/node-tests/unit/plugins/lint-block-indentation-test.js
+++ b/node-tests/unit/plugins/lint-block-indentation-test.js
@@ -49,6 +49,23 @@ generateRuleTests({
       '  <p>Hi!</p>\n' +
       '{{/if}}',
     '{{#if foo}}<p>Hi!</p>{{/if}}',
+    '<div>\n' +
+      '  <span>Foo</span>{{#some-thing}}<p>lorum ipsum</p>{{/some-thing}}\n' +
+      '</div>',
+    '{{#if foo}}\n' +
+      '  {{foo}}-{{bar}}\n' +
+      '{{/if}}',
+    '{{#if foo}}\n' +
+      '  Foo-{{bar}}\n' +
+      '{{/if}}',
+    '{{#if foo}}\n' +
+      '  Foo:\n' +
+      '  {{bar}}\n' +
+      '{{/if}}',
+    '{{#if foo}}\n' +
+      '  {{foo}}:\n' +
+      '  {{bar}}\n' +
+      '{{/if}}',
     {
       config: 4,
 
@@ -72,12 +89,12 @@ generateRuleTests({
       // start and end must be the same indentation
       template: '\n  {{#each cats as |dog|}}\n        {{/each}}',
 
-      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."
+      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."
     },
     {
       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."
+      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."
     },
     {
       template: '<div>\n<p>Stuff goes here</p>\n</div>',
@@ -97,7 +114,7 @@ generateRuleTests({
         '    {{/if}}\n' +
         '{{/if}}',
 
-      message: "Incorrect indentation for `if` beginning at ('layout.hbs'@ L3:C2). Expected `{{/if}}` ending at ('layout.hbs'@ L5:C11)to be at an indentation of 2 but was found at 4."
+      message: "Incorrect indentation for `if` beginning at ('layout.hbs'@ L3:C2). Expected `{{/if}}` ending at ('layout.hbs'@ L5:C11) to be at an indentation of 2 but was found at 4."
     },
     {
       config: 4,
@@ -111,17 +128,38 @@ generateRuleTests({
     },
     {
       template: '<div>\n' +
-        '  <span></span>{{test}}\n' +
+        '  {{foo}}\n' +
+        '{{bar}}\n' +
         '</div>',
 
-      message: "Incorrect indentation for `{{test}}` beginning at ('layout.hbs'@ L2:C15). Expected `{{test}}` to be at an indentation of 2 but was found at 15."
+      message: "Incorrect indentation for `{{bar}}` beginning at ('layout.hbs'@ L3:C0). Expected `{{bar}}` to be at an indentation of 2 but was found at 0."
     },
     {
       template: '<div>\n' +
-        '  <span></span>{{#test-foo}}{{/test-foo}}\n' +
+        '  Foo:\n' +
+        '{{bar}}\n' +
         '</div>',
 
-      message: "Incorrect indentation for `{{#test-foo}}` beginning at ('layout.hbs'@ L2:C15). Expected `{{#test-foo}}` to be at an indentation of 2 but was found at 15."
+      message: "Incorrect indentation for `{{bar}}` beginning at ('layout.hbs'@ L3:C0). Expected `{{bar}}` to be at an indentation of 2 but was found at 0."
+    },
+    {
+      // Start and end of multi-line block must be aligned, even when start
+      // has other content preceding it on its line
+      template: '<div>\n' +
+        '  <span>Foo</span>{{#some-thing}}\n' +
+        '  {{/some-thing}}\n' +
+        '</div>',
+      message: "Incorrect indentation for `some-thing` beginning at ('layout.hbs'@ L2:C18). Expected `{{/some-thing}}` ending at ('layout.hbs'@ L3:C17) to be at an indentation of 18 but was found at 2."
+    },
+    {
+      // Start and end of multi-line element must be aligned, even when start
+      // has other content preceding it on its line
+      template: '{{#if foo}}\n' +
+        '  {{foo}} <p>\n' +
+        '    Bar\n' +
+        '  </p>\n' +
+        '{{/if}}',
+      message: "Incorrect indentation for `p` beginning at ('layout.hbs'@ L2:C10). Expected `</p>` ending at ('layout.hbs'@ L4:C6) to be at an indentation of 10 but was found at 2."
     }
   ]
 });