Repository: rwjblue/ember-template-lint
Pull request #304 (2017-10-16 16:55:50)

add no-trailing-spaces rule

rwjblue: @kellyselden - Looks like this needs a rebase...

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7ae13e..1b9ca6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,24 @@
 Changelog
 =========
 
+- Add new `no-trailing-spaces` rule. Examples:
+
+Bad:
+
+```hbs
+<div>test</div>//••
+//•••••
+```
+
+Good:
+
+```hbs
+<div>test</div>//
+//
+```
+
+You can read more about the rule [in the documentation](https://github.com/rwjblue/ember-template-lint/blob/master/docs/rule/no-trailing-spaces.md).
+
 - Add new `eol-last` rule. Examples:
 
 Enforce either:
diff --git a/docs/rule/no-trailing-spaces.md b/docs/rule/no-trailing-spaces.md
new file mode 100644
index 0000000..2df209a
--- /dev/null
+++ b/docs/rule/no-trailing-spaces.md
@@ -0,0 +1,17 @@
+#### no-trailing-spaces
+
+Disallow trailing whitespace at the end of lines. Similar to https://eslint.org/docs/rules/no-trailing-spaces.
+
+Bad:
+
+```hbs
+<div>test</div>//••
+//•••••
+```
+
+Good:
+
+```hbs
+<div>test</div>//
+//
+```
diff --git a/docs/rules.md b/docs/rules.md
index 9ececfd..701d809 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -12,8 +12,9 @@
 * [link-rel-noopener](rule/link-rel-noopener.md)
 * [nested-interactive](rule/nested-interactive.md)
 * [no-debugger](rule/no-debugger.md)
-* [no-log](rule/no-log.md)
 * [no-duplicate-attributes](rule/unused-block-params.md)
+* [no-log](rule/no-log.md)
+* [no-trailing-spaces](rule/no-trailing-spaces.md)
 * [self-closing-void-elements](rule/self-closing-void-elements.md)
 * [simple-unless](rule/simple-unless.md)
 * [style-concatenation](rule/style-concatenation.md)
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 2cbbf33..0bd0e2a 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -24,5 +24,6 @@ module.exports = {
   'attribute-indentation': require('./lint-attribute-indentation'),
   'template-length': require('./lint-template-length'),
   'table-groups': require('./lint-table-groups'),
-  'eol-last': require('./lint-eol-last')
+  'eol-last': require('./lint-eol-last'),
+  'no-trailing-spaces': require('./lint-no-trailing-spaces')
 };
diff --git a/lib/rules/lint-no-trailing-spaces.js b/lib/rules/lint-no-trailing-spaces.js
new file mode 100644
index 0000000..e0d60fe
--- /dev/null
+++ b/lib/rules/lint-no-trailing-spaces.js
@@ -0,0 +1,51 @@
+'use strict';
+
+const Rule = require('./base');
+
+module.exports = class NoTrailingSpaces extends Rule {
+  visitor() {
+    return {
+      Program: {
+
+        // implementation goes here in exit(): in the exit handler, the rule will not
+        // be called if it has been disabled by any inline comments within the file.
+
+        exit(node) {
+          let line = node.loc.start.line;
+          let column = node.loc.start.column;
+
+          // yielded content makes it in here too
+          //
+          // `{{#my-component}}
+          //   test
+          // {{/my-component}}`
+          //
+          // becomes
+          //
+          // `
+          //   test
+          // `
+          //
+          // check for that case
+          if (line !== 1 || column !== 0) {
+            return;
+          }
+
+          let source = this.sourceForNode(node);
+
+          source.split('\n').forEach((line, i) => {
+            let column = line.length - 1;
+            if (line[column] === ' ') {
+              this.log({
+                message: 'line cannot end with space',
+                line: i + 1,
+                column,
+                source: line
+              });
+            }
+          });
+        }
+      }
+    };
+  }
+};


Test patch:
diff --git a/test/unit/rules/lint-no-trailing-spaces-test.js b/test/unit/rules/lint-no-trailing-spaces-test.js
new file mode 100644
index 0000000..b4ee818
--- /dev/null
+++ b/test/unit/rules/lint-no-trailing-spaces-test.js
@@ -0,0 +1,72 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'no-trailing-spaces',
+
+  config: true,
+
+  good: [
+    'test',
+    'test\n',
+    'test\n' +
+    '\n',
+    // test the re-entering of yielded content
+    '{{#my-component}}\n' +
+    '  test\n' +
+    '{{/my-component}}'
+  ],
+
+  bad: [
+    {
+      template: 'test ',
+
+      result: {
+        moduleId: 'layout.hbs',
+        message: 'line cannot end with space',
+        line: 1,
+        column: 4,
+        source: 'test '
+      }
+    },
+    {
+      template: 'test \n',
+
+      result: {
+        moduleId: 'layout.hbs',
+        message: 'line cannot end with space',
+        line: 1,
+        column: 4,
+        source: 'test '
+      }
+    },
+    {
+      template: 'test\n' +
+      ' \n',
+
+      result: {
+        moduleId: 'layout.hbs',
+        message: 'line cannot end with space',
+        line: 2,
+        column: 0,
+        source: ' '
+      }
+    },
+    // test the re-entering of yielded content
+    // only generates one error instead of two
+    {
+      template: '{{#my-component}}\n' +
+      '  test \n' +
+      '{{/my-component}}',
+
+      result: {
+        moduleId: 'layout.hbs',
+        message: 'line cannot end with space',
+        line: 2,
+        column: 6,
+        source: '  test '
+      }
+    }
+  ]
+});