Repository: ember-template-lint/ember-template-lint
Pull request #461 (2018-07-26 17:47:13)

Add no-quoteless-attributes rule.
Closes https://github.com/ember-template-lint/ember-template-lint/issues/456


Patch:
diff --git a/docs/rule/no-quoteless-attributes.md b/docs/rule/no-quoteless-attributes.md
new file mode 100644
index 00000000..5d578d2a
--- /dev/null
+++ b/docs/rule/no-quoteless-attributes.md
@@ -0,0 +1,29 @@
+## no-quoteless-attributes
+
+In HTML all attribute values are considered strings, regardless if they were quoted or not initially.
+
+The following two examples are _identical_ from the perspective of the browser:
+
+```html
+<div data-foo=asdf></div>
+<div data-foo="asdf"></div>
+```
+
+This fact makes the following HTML very confusing:
+
+```html
+<input disabled=false>
+```
+
+In this case, the simple _presence_ of the `disabled` attribute means that the
+`<input>` is disabled and setting the value to `false` doesn't do the obvious
+thing.
+
+This is just _one_ (of many) cases where the default "string" based parsing of
+attributes in HTML can trip folks up.
+
+The `no-quoteless-attributes` rule attempts to make this situation _slightly_
+better by at least ensuring that all attribute values are quoted. This
+obviously doesn't fix the :troll:y nature of HTML here but it does ensure that
+you still **see** the quotes (which should hopefully help remind you that
+these are strings and not values).
diff --git a/docs/rules.md b/docs/rules.md
index 44242a09..4e45049f 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -30,6 +30,7 @@
 * [no-triple-curlies](rule/no-triple-curlies.md)
 * [no-unused-block-params](rule/no-unused-block-params.md)
 * [no-implicit-this](rule/no-implicit-this.md)
+* [no-quoteless-attributes](rule/no-quoteless-attributes.md)
 
 ## Deprecations
 
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 4a52cf41..f8e9f92a 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -40,4 +40,5 @@ module.exports = {
   'no-attrs-in-components': require('./lint-no-attrs-in-components'),
   'no-implicit-this': require('./lint-no-implicit-this'),
   'no-shadowed-elements': require('./lint-no-shadowed-elements'),
+  'no-quoteless-attributes': require('./lint-no-quoteless-attributes'),
 };
diff --git a/lib/rules/lint-no-quoteless-attributes.js b/lib/rules/lint-no-quoteless-attributes.js
new file mode 100644
index 00000000..c60be479
--- /dev/null
+++ b/lib/rules/lint-no-quoteless-attributes.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const Rule = require('./base');
+
+module.exports = class NoPartial extends Rule {
+  visitor() {
+    return {
+      ElementNode(node) {
+        node.attributes.forEach(attribute => {
+          let value = attribute.value;
+
+          if (value.type !== 'TextNode') { return; }
+
+          let isValueless = value.loc &&
+            value.loc.start.line === value.loc.end.line &&
+            value.loc.start.column === value.loc.end.column;
+
+          if (isValueless) { return; }
+
+          let source = this.sourceForNode(attribute.value);
+          let isQuoted = source[0] === '"' || source[0] === '\'';
+
+          if (!isQuoted) {
+            this.log({
+              message: `Attribute ${attribute.name} should be either quoted or wrapped in mustaches`,
+              line: value.loc && value.loc.start.line,
+              column: value.loc && value.loc.start.column,
+              source: this.sourceForNode(node)
+            });
+          }
+        });
+      },
+    };
+  }
+};


Test patch:
diff --git a/test/unit/rules/lint-no-quoteless-attributes-test.js b/test/unit/rules/lint-no-quoteless-attributes-test.js
new file mode 100644
index 00000000..d86f8966
--- /dev/null
+++ b/test/unit/rules/lint-no-quoteless-attributes-test.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'no-quoteless-attributes',
+
+  config: true,
+
+  good: [
+    '<div data-foo="derp"></div>',
+    '<div data-foo="derp {{stuff}}"></div>',
+    '<div data-foo={{someValue}}></div>',
+    '<div data-foo={{true}}></div>',
+    '<div data-foo={{false}}></div>',
+    '<div data-foo={{5}}></div>',
+    '<SomeThing ...attributes />',
+    '<div></div>',
+    '<input disabled>',
+  ],
+
+  bad: [
+    {
+      template: '<div data-foo=asdf></div>',
+
+      result: {
+        column: 14,
+        line: 1,
+        message: 'Attribute data-foo should be either quoted or wrapped in mustaches',
+        source: '<div data-foo=asdf></div>',
+      }
+    },
+    {
+      template: '<SomeThing @blah=asdf />',
+
+      result: {
+        column: 17,
+        line: 1,
+        message: 'Attribute @blah should be either quoted or wrapped in mustaches',
+        source: '<SomeThing @blah=asdf />',
+      }
+    },
+  ]
+});