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

Issue:
{{if}} helper gets error for component syntax
```
      {{if (or logout.isRunning (not session.isAuthenticated)) "Logging Out..." "Log Out"}}
```

results in the following errors:
```
  8:11  error  Incorrect indentation of positional param 'undefined' beginning at L8:C11. Expected 'undefined' to be indentation at an of 8 but was found at 11  attribute-indentation
  8:63  error  Incorrect indentation of positional param 'Logging Out...' beginning at L8:C63. Expected 'Logging Out...' to be indentation at an of 8 but was found at 63  attribute-indentation
  8:80  error  Incorrect indentation of positional param 'Log Out' beginning at L8:C80. Expected 'Log Out' to be indentation at an of 8 but was found at 80  attribute-indentation
  8:89  error  Incorrect indentation of close curly braces '}}' for the component '{{if}}' beginning at L8:C89. Expected to be indentation at L2:C6 with an of 6 but was found at 89  attribute-indentation
```

but `if` is a helper in this context and is not a component. Breaking this out into
```
      {{#if (or logout.isRunning (not session.isAuthenticated))}}
        Logging Out
      {{else}}
        Log out
      {{/}}
```
gets rid of all lint errors.

I'm using version `0.8.14` on `ember-cli` version `2.16.2`, and using the following `.template-lintrc.js`:
```
module.exports = {
  // https://github.com/rwjblue/ember-template-lint/blob/master/lib/config/recommended.js
  extends: 'recommended',

  rules: {
    // enforces double quotes!
    'quotes': 'double',

    // attribute indentation forces multiline attributes when they exceed 80 characters
    'attribute-indentation': true,

    // eol-last (always) enforces files end with new lines
    'eol-last': 'always',

    'no-trailing-spaces': true,

    // ensures tables are inserted with thead/tbody to avoid Glimmer issues
    'table-groups': true,
  }
}
```

Pull request description:
357 fix helper error
- #### validateCloseBrace can give an exact location
- #### fix error messages:
```
Incorrect indentation of positional param 'undefined' beginning at L8:C11. Expected 'undefined' to be indentation at an of 8 but was found at 11  attribute-indentation
Incorrect indentation of positional param 'Logging Out...' beginning at L8:C63. Expected 'Logging Out...' to be indentation at an of 8 but was found at 63  attribute-indentation
Incorrect indentation of positional param 'Log Out' beginning at L8:C80. Expected 'Log Out' to be indentation at an of 8 but was found at 80  attribute-indentation
Incorrect indentation of close curly braces '}}' for the component '{{if}}' beginning at L8:C89. Expected to be indentation at L2:C6 with an of 6 but was found at 89  attribute-indentation

```


```
Incorrect indentation of positional param 'or' beginning at L1:C5. Expected 'or' to be at L2:C2.
Incorrect indentation of positional param 'Logging Out...' beginning at L1:C57. Expected 'Logging Out...' to be at L3:C2.
Incorrect indentation of positional param 'Log Out' beginning at L1:C74. Expected 'Log Out' to be at L4:C2.
Incorrect indentation of close curly braces '}}' for the component '{{if}}' beginning at L1:C83. Expected '{{if}}' to be at L5:C0.
```

closes #357 

Patch:
diff --git a/docs/rule/attribute-indentation.md b/docs/rule/attribute-indentation.md
index 5a52e3d..e298ff2 100644
--- a/docs/rule/attribute-indentation.md
+++ b/docs/rule/attribute-indentation.md
@@ -2,7 +2,7 @@
 
 This rule requires the positional params, attributes and block params of the helper/component to be indented by moving them to multiple lines when the open invocation has more than 80 characters (configurable).
 
-Forbidden:
+#### Forbidden:
 
 Non-Block form
 ``` hbs
@@ -18,7 +18,7 @@ Block form
   {{/employee-details}}
 ```
 
-Allowed:
+#### Allowed:
 
 Non-Block form
 ``` hbs
@@ -31,12 +31,33 @@ Non-Block form
   }}
 ```
 
-Non-Block Form (open invocation < 80 characters)
+Non-Block form (open invocation < 80 characters)
 ``` hbs
 
   {{employee-details firstName=firstName lastName=lastName}}
 ```
 
+Non-Block form with Helper
+```hbs
+  {{if
+    (or logout.isRunning (not session.isAuthenticated))
+    "Logging Out..."
+    "Log Out"
+  }}
+```
+
+Non-Block form with Helper unfolded
+```hbs
+  {{if
+    (or
+      logout.isRunning
+      (not session.isAuthenticated)
+    )
+    "Logging Out..."
+    "Log Out"
+  }}
+```
+
 Block form
 ``` hbs
 
@@ -50,7 +71,7 @@ Block form
   {{/employee-details}}
 ```
 
-Block Form (open invocation < 80 characters)
+Block form (open invocation < 80 characters)
 ``` hbs
 
   {{#employee-details firstName=firstName lastName=lastName as |employee|}}
@@ -58,7 +79,6 @@ Block Form (open invocation < 80 characters)
   {{/employee-details}}
 ```
 
-The following values are valid configuration:
-
+#### Configuration:
   * boolean - `true` - Enables the rule to be enforced when the opening invocation has more than 80 characters or when it spans multiple lines.
   * object - { 'open-invocation-max-len': n characters } - Maximum length of the opening invocation.
diff --git a/lib/rules/lint-attribute-indentation.js b/lib/rules/lint-attribute-indentation.js
index bd4bf8e..dcaad7e 100644
--- a/lib/rules/lint-attribute-indentation.js
+++ b/lib/rules/lint-attribute-indentation.js
@@ -217,7 +217,7 @@ module.exports = class AttributeSpacing extends Rule {
 
       let blockParamStatement = this.sourceForNode({ loc: { start: actual, end: node.program.loc.start } }).trim();
 
-      let message = `Incorrect indentation of block params '${blockParamStatement}' beginning at L${actual.line}:C${actual.column}. Expecting the block params to be at L${expected.line}:C${expected.column} with an indentation of ${expected.column} but was found at ${actual.column}.`;
+      let message = `Incorrect indentation of block params '${blockParamStatement}' beginning at L${actual.line}:C${actual.column}. Expecting the block params to be at L${expected.line}:C${expected.column}.`;
 
       this.log({
         message,
@@ -249,8 +249,8 @@ module.exports = class AttributeSpacing extends Rule {
       let actualStartLocation = param.loc.start;
       if (expectedLineStart !== actualStartLocation.line ||
         expectedColumnStart !== actualStartLocation.column) {
-        let paramName = param[namePath];
-        let message = `Incorrect indentation of ${paramType} '${paramName}' beginning at L${actualStartLocation.line}:C${actualStartLocation.column}. Expected '${paramName}' to be at L${expectedLineStart}:C${expectedColumnStart} with an indentation of ${expectedColumnStart} but was found at ${actualStartLocation.column}`;
+        let paramName = param[namePath] ? param[namePath] : param.path.original;
+        let message = `Incorrect indentation of ${paramType} '${paramName}' beginning at L${actualStartLocation.line}:C${actualStartLocation.column}. Expected '${paramName}' to be at L${expectedLineStart}:C${expectedColumnStart}.`;
         this.log({
           message,
           line: actualStartLocation.line,
@@ -262,7 +262,9 @@ module.exports = class AttributeSpacing extends Rule {
       const type = param.value ? param.value.type : param.type;
       if (type === 'SubExpression') {
         //TODO check subexpressions
-        expectedLineStart = param.loc.end.line;
+        if (param.loc.start.line !== param.loc.end.line) {
+          expectedLineStart = param.loc.end.line;
+        }
       }
       expectedLineStart++;
     });
@@ -283,10 +285,12 @@ module.exports = class AttributeSpacing extends Rule {
     let expectedLineStart = node.loc.start.line + 1;
 
     expectedLineStart = this.iterateParams(node.params, 'positional', expectedLineStart, expectedColumnStart, node);
-    this.iterateParams(node.hash.pairs, 'attribute', expectedLineStart, expectedColumnStart, node);
+    expectedLineStart = this.iterateParams(node.hash.pairs, 'attribute', expectedLineStart, expectedColumnStart, node);
+    
+    return expectedLineStart;
   }
 
-  validateCloseBrace(node) {
+  validateCloseBrace(node, expectedStartLine) {
     /*
       Validates the close brace (`}}`) of the non-block form.
     */
@@ -295,12 +299,6 @@ module.exports = class AttributeSpacing extends Rule {
       column: node.loc.end.column - 2
     };
 
-    let expectedStartLine = node.hash.loc.end.line + 1;
-    const lastPositionalParam = node.params.pop();
-    if (lastPositionalParam && lastPositionalParam.loc.end.line > node.hash.loc.end.line) {
-      expectedStartLine = lastPositionalParam.loc.end.line + 1;
-    }
-
     const expectedStartLocation = {
       line: expectedStartLine,
       column: node.loc.start.column
@@ -309,7 +307,7 @@ module.exports = class AttributeSpacing extends Rule {
     let componentName = node.path.original;
     if (actualStartLocation.line !== expectedStartLocation.line ||
       actualStartLocation.column !== expectedStartLocation.column) {
-      let message = `Incorrect indentation of close curly braces '}}' for the component '{{${componentName}}}' beginning at L${actualStartLocation.line}:C${actualStartLocation.column}. Expected to be indentation at L${expectedStartLocation.line}:C${expectedStartLocation.column} with an of ${expectedStartLocation.column} but was found at ${actualStartLocation.column}`;
+      let message = `Incorrect indentation of close curly braces '}}' for the component '{{${componentName}}}' beginning at L${actualStartLocation.line}:C${actualStartLocation.column}. Expected '{{${componentName}}}' to be at L${expectedStartLocation.line}:C${expectedStartLocation.column}.`;
 
       this.log({
         message,
@@ -324,8 +322,8 @@ module.exports = class AttributeSpacing extends Rule {
   validateNonBlockForm(node) {
     // no need to validate if no positional and named params are present.
     if (node.params.length || node.hash.pairs.length) {
-      this.validateParamsAndHashPairs(node);
-      this.validateCloseBrace(node);
+      const expectedStartLine = this.validateParamsAndHashPairs(node);
+      this.validateCloseBrace(node, expectedStartLine);
     }
   }
 


Test patch:
diff --git a/test/unit/rules/lint-attribute-indentation-test.js b/test/unit/rules/lint-attribute-indentation-test.js
index e886dc4..539468c 100644
--- a/test/unit/rules/lint-attribute-indentation-test.js
+++ b/test/unit/rules/lint-attribute-indentation-test.js
@@ -32,6 +32,21 @@ generateRuleTests({
     '  firstName' + '\n' +
     '  lastName' + '\n' +
     '}}',
+    // helper
+    '{{if' + '\n' +
+    '  (or logout.isRunning (not session.isAuthenticated))' + '\n' +
+    '  "Logging Out..."' + '\n' +
+    '  "Log Out"' + '\n' +
+    '}}',
+    // helper unfolded
+    '{{if' + '\n' +
+    '  (or ' + '\n' +
+    '    logout.isRunning' + '\n' +
+    '    (not session.isAuthenticated)' + '\n' +
+    '  )' + '\n' +
+    '  "Logging Out..."' + '\n' +
+    '  "Log Out"' + '\n' +
+    '}}',
     // positional null
     '{{contact-null' + '\n' +
     '  null' + '\n' +
@@ -137,19 +152,19 @@ generateRuleTests({
     results: [{
       'column': 18,
       'line': 1,
-      'message': 'Incorrect indentation of attribute \'firstName\' beginning at L1:C18. Expected \'firstName\' to be at L2:C2 with an indentation of 2 but was found at 18',
+      'message': 'Incorrect indentation of attribute \'firstName\' beginning at L1:C18. Expected \'firstName\' to be at L2:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{contact-details firstName=firstName lastName=lastName}}'
     }, {
       'column': 38,
       'line': 1,
-      'message': 'Incorrect indentation of attribute \'lastName\' beginning at L1:C38. Expected \'lastName\' to be at L3:C2 with an indentation of 2 but was found at 38',
+      'message': 'Incorrect indentation of attribute \'lastName\' beginning at L1:C38. Expected \'lastName\' to be at L3:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{contact-details firstName=firstName lastName=lastName}}'
     }, {
       'column': 55,
       'line': 1,
-      'message': 'Incorrect indentation of close curly braces \'}}\' for the component \'{{contact-details}}\' beginning at L1:C55. Expected to be indentation at L2:C0 with an of 0 but was found at 55',
+      'message': 'Incorrect indentation of close curly braces \'}}\' for the component \'{{contact-details}}\' beginning at L1:C55. Expected \'{{contact-details}}\' to be at L4:C0.',
       'moduleId': 'layout.hbs',
       'source': '{{contact-details firstName=firstName lastName=lastName}}'
     }]
@@ -162,19 +177,19 @@ generateRuleTests({
     results: [{
       'column': 1,
       'line': 2,
-      'message': `Incorrect indentation of attribute 'firstName' beginning at L2:C1. Expected 'firstName' to be at L2:C2 with an indentation of 2 but was found at 1`,
+      'message': `Incorrect indentation of attribute 'firstName' beginning at L2:C1. Expected 'firstName' to be at L2:C2.`,
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details\n firstName=firstName lastName=lastName as |contact|}}\n {{contact.fullName}}\n{{/contact-details}}'
     }, {
       'column': 21,
       'line': 2,
-      'message': 'Incorrect indentation of attribute \'lastName\' beginning at L2:C21. Expected \'lastName\' to be at L3:C2 with an indentation of 2 but was found at 21',
+      'message': 'Incorrect indentation of attribute \'lastName\' beginning at L2:C21. Expected \'lastName\' to be at L3:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details\n firstName=firstName lastName=lastName as |contact|}}\n {{contact.fullName}}\n{{/contact-details}}'
     }, {
       'column': 38,
       'line': 2,
-      'message': 'Incorrect indentation of block params \'as |contact|}}\' beginning at L2:C38. Expecting the block params to be at L3:C0 with an indentation of 0 but was found at 38.',
+      'message': 'Incorrect indentation of block params \'as |contact|}}\' beginning at L2:C38. Expecting the block params to be at L3:C0.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details\n firstName=firstName lastName=lastName as |contact|}}\n {{contact.fullName}}\n{{/contact-details}}'
     }]
@@ -186,31 +201,31 @@ generateRuleTests({
     results: [{
       'column': 19,
       'line': 1,
-      'message': 'Incorrect indentation of attribute \'firstName\' beginning at L1:C19. Expected \'firstName\' to be at L2:C2 with an indentation of 2 but was found at 19',
+      'message': 'Incorrect indentation of attribute \'firstName\' beginning at L1:C19. Expected \'firstName\' to be at L2:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details firstName=firstName lastName=lastName age=age avatar=avatar as |contact|}}\n  {{fullName}}\n{{/contact-details}}'
     }, {
       'column': 39,
       'line': 1,
-      'message': 'Incorrect indentation of attribute \'lastName\' beginning at L1:C39. Expected \'lastName\' to be at L3:C2 with an indentation of 2 but was found at 39',
+      'message': 'Incorrect indentation of attribute \'lastName\' beginning at L1:C39. Expected \'lastName\' to be at L3:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details firstName=firstName lastName=lastName age=age avatar=avatar as |contact|}}\n  {{fullName}}\n{{/contact-details}}'
     }, {
       'column': 57,
       'line': 1,
-      'message': 'Incorrect indentation of attribute \'age\' beginning at L1:C57. Expected \'age\' to be at L4:C2 with an indentation of 2 but was found at 57',
+      'message': 'Incorrect indentation of attribute \'age\' beginning at L1:C57. Expected \'age\' to be at L4:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details firstName=firstName lastName=lastName age=age avatar=avatar as |contact|}}\n  {{fullName}}\n{{/contact-details}}'
     }, {
       'column': 65,
       'line': 1,
-      'message': 'Incorrect indentation of attribute \'avatar\' beginning at L1:C65. Expected \'avatar\' to be at L5:C2 with an indentation of 2 but was found at 65',
+      'message': 'Incorrect indentation of attribute \'avatar\' beginning at L1:C65. Expected \'avatar\' to be at L5:C2.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details firstName=firstName lastName=lastName age=age avatar=avatar as |contact|}}\n  {{fullName}}\n{{/contact-details}}'
     }, {
       'column': 78,
       'line': 1,
-      'message': 'Incorrect indentation of block params \'as |contact|}}\' beginning at L1:C78. Expecting the block params to be at L2:C0 with an indentation of 0 but was found at 78.',
+      'message': 'Incorrect indentation of block params \'as |contact|}}\' beginning at L1:C78. Expecting the block params to be at L2:C0.',
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details firstName=firstName lastName=lastName age=age avatar=avatar as |contact|}}\n  {{fullName}}\n{{/contact-details}}'
     }]
@@ -225,10 +240,41 @@ generateRuleTests({
     results: [{
       'column': 0,
       'line': 4,
-      'message': `Incorrect indentation of block params 'as |contact|}}' beginning at L4:C0. Expecting the block params to be at L2:C0 with an indentation of 0 but was found at 0.`,
+      'message': `Incorrect indentation of block params 'as |contact|}}' beginning at L4:C0. Expecting the block params to be at L2:C0.`,
       'moduleId': 'layout.hbs',
       'source': '{{#contact-details\n\n\nas |contact|}}\n  {{contact.fullName}}\n{{/contact-details}}'
     }]
 
+  },{
+    // with helper, non-block, > 80 chars
+    config: {
+      'open-invocation-max-len': 80
+    },
+    template: '{{if (or logout.isRunning (not session.isAuthenticated)) "Logging Out..." "Log Out"}}',
+    results: [{
+      'column': 5,
+      'line': 1,
+      'message': 'Incorrect indentation of positional param \'or\' beginning at L1:C5. Expected \'or\' to be at L2:C2.',
+      'moduleId': 'layout.hbs',
+      'source': '{{if (or logout.isRunning (not session.isAuthenticated)) "Logging Out..." "Log Out"}}'
+    }, {
+      'column': 57,
+      'line': 1,
+      'message': 'Incorrect indentation of positional param \'Logging Out...\' beginning at L1:C57. Expected \'Logging Out...\' to be at L3:C2.',
+      'moduleId': 'layout.hbs',
+      'source': '{{if (or logout.isRunning (not session.isAuthenticated)) "Logging Out..." "Log Out"}}'
+    }, {
+      'column': 74,
+      'line': 1,
+      'message': 'Incorrect indentation of positional param \'Log Out\' beginning at L1:C74. Expected \'Log Out\' to be at L4:C2.',
+      'moduleId': 'layout.hbs',
+      'source': '{{if (or logout.isRunning (not session.isAuthenticated)) "Logging Out..." "Log Out"}}'
+    }, {
+      'column': 83,
+      'line': 1,
+      'message': 'Incorrect indentation of close curly braces \'}}\' for the component \'{{if}}\' beginning at L1:C83. Expected \'{{if}}\' to be at L5:C0.',
+      'moduleId': 'layout.hbs',
+      'source': '{{if (or logout.isRunning (not session.isAuthenticated)) "Logging Out..." "Log Out"}}'
+    }]
   }]
 });