Repository: ember-template-lint/ember-template-lint
Pull request #733 (2019-06-05 19:23:11)

Add new rule: no-obsolete-elements
If merged, this PR adds a new rule to check for obsolete elements and throw an error if they are used. 


Patch:
diff --git a/docs/rule/no-obsolete-elements.md b/docs/rule/no-obsolete-elements.md
new file mode 100644
index 00000000..a1f21430
--- /dev/null
+++ b/docs/rule/no-obsolete-elements.md
@@ -0,0 +1,51 @@
+## no-obsolete-elements
+
+Some elements are entirely obsolete and must not be used by authors. 
+
+This rule forbids the use of obsolete elements.
+
+### Examples
+
+This rule **forbids** the following:
+
+```hbs
+<applet></applet>
+<acronym></acronym>
+<bgsound></bgsound>
+<dir></dir>
+<frame></frame>
+<frameset></frameset>
+<noframes></noframes>
+<isindex></isindex>
+<keygen>
+<listing></listing>
+<menuitem></menuitem>
+<nextid></nextid>
+<noembed></noembed>
+<plaintext></plaintext>
+<rb></rb>
+<rtc></rtc>
+<strike></strike>
+<xmp></xmp>
+<basefont></basefont>
+<big></big>
+<blink></blink>
+<center></center>
+<font></font>
+<marquee></marquee>
+<multicol></multicol>
+<nobr></nobr>
+<spacer></spacer>
+<tt></tt>
+```
+
+This rule **allows** anything that is not an obsolete element.
+
+### Migration
+
+* replace any use of these elements with the appropriate updated element or a `div` element.
+
+### References
+
+* [no-obsolete-elements](https://html.spec.whatwg.org/multipage/obsolete.html#non-conforming-features)
+* [Failure of Success Criterion 2.2.2 due to using the blink element](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F47)
diff --git a/docs/rules.md b/docs/rules.md
index 4049abc6..9b08d5d5 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -27,6 +27,7 @@
 * [no-log](rule/no-log.md)
 * [no-negated-condition](rule/no-negated-condition.md)
 * [no-nested-interactive](rule/no-nested-interactive.md)
+* [no-obsolete-elements](rule/no-obsolete-elements.md)
 * [no-outlet-outside-routes](rule/no-outlet-outside-routes.md)
 * [no-positive-tabindex](rule/no-positive-tabindex.md)
 * [no-partial](rule/no-partial.md)
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 60f5171b..e4d3dca0 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -32,6 +32,7 @@ module.exports = {
   'no-log': require('./lint-no-log'),
   'no-negated-condition': require('./lint-no-negated-condition'),
   'no-nested-interactive': require('./lint-no-nested-interactive'),
+  'no-obsolete-elements': require('./lint-no-obsolete-elements'),
   'no-outlet-outside-routes': require('./lint-no-outlet-outside-routes'),
   'no-partial': require('./lint-no-partial'),
   'no-positive-tabindex': require('./lint-no-positive-tabindex'),
diff --git a/lib/rules/lint-no-invalid-interactive.js b/lib/rules/lint-no-invalid-interactive.js
index fe1976c4..7031023d 100644
--- a/lib/rules/lint-no-invalid-interactive.js
+++ b/lib/rules/lint-no-invalid-interactive.js
@@ -89,6 +89,11 @@ module.exports = class InvalidInteractive extends Rule {
             return;
           }
 
+          // Allow onerror={{action "foo"}} on img tags
+          if (this._element.tag === 'img' && node.name === 'onerror') {
+            return;
+          }
+
           this.log({
             message: 'Interaction added to non-interactive element',
             line: node.loc && node.loc.start.line,
diff --git a/lib/rules/lint-no-obsolete-elements.js b/lib/rules/lint-no-obsolete-elements.js
new file mode 100644
index 00000000..00445b1f
--- /dev/null
+++ b/lib/rules/lint-no-obsolete-elements.js
@@ -0,0 +1,57 @@
+'use strict';
+
+const Rule = require('./base');
+
+const ERROR_MESSAGE_OBSOLETE_ELEMENT = element =>
+  `Use of <${element}> detected. Do not use deprecated elements.`;
+
+// https://html.spec.whatwg.org/multipage/obsolete.html#non-conforming-features
+const OBSOLETE_ELEMENTS = [
+  'applet',
+  'acronym',
+  'bgsound',
+  'dir',
+  'frame',
+  'frameset',
+  'noframes',
+  'isindex',
+  'listing',
+  'menuitem',
+  'nextid',
+  'noembed',
+  'plaintext',
+  'rb',
+  'rtc',
+  'strike',
+  'xmp',
+  'basefont',
+  'big',
+  'blink',
+  'center',
+  'font',
+  'marquee',
+  'multicol',
+  'nobr',
+  'spacer',
+  'tt',
+];
+
+module.exports = class NoObsoleteElements extends Rule {
+  visitor() {
+    return {
+      ElementNode(node) {
+        if (!this.isLocal(node) && OBSOLETE_ELEMENTS.includes(node.tag)) {
+          this.log({
+            message: ERROR_MESSAGE_OBSOLETE_ELEMENT(node.tag),
+            line: node.loc && node.loc.start.line,
+            column: node.loc && node.loc.start.column,
+            source: this.sourceForNode(node),
+          });
+        }
+      },
+    };
+  }
+};
+
+module.exports.ERROR_MESSAGE_OBSOLETE_ELEMENT = ERROR_MESSAGE_OBSOLETE_ELEMENT;
+module.exports.OBSOLETE_ELEMENTS = OBSOLETE_ELEMENTS;


Test patch:
diff --git a/test/unit/rules/lint-no-invalid-interactive-test.js b/test/unit/rules/lint-no-invalid-interactive-test.js
index ea26c765..b30fad3d 100644
--- a/test/unit/rules/lint-no-invalid-interactive-test.js
+++ b/test/unit/rules/lint-no-invalid-interactive-test.js
@@ -15,6 +15,7 @@ generateRuleTests({
     '<form onsubmit={{action "foo"}}></form>',
     '<form {{action "foo" on="reset"}}></form>',
     '<form onreset={{action "foo"}}></form>',
+    '<img onerror={{action "foo"}}>',
     '<InputSearch @onInput={{action "foo"}} />',
     '<InputSearch @onInput={{action "foo"}}></InputSearch>',
     '{{#with (hash bar=(component "foo")) as |foo|}}<foo.bar @onInput={{action "foo"}}></foo.bar>{{/with}}',
@@ -26,6 +27,10 @@ generateRuleTests({
       config: { additionalInteractiveTags: ['div'] },
       template: '<div onclick={{action "foo"}}></div>',
     },
+    {
+      config: { additionalInteractiveTags: ['img'] },
+      template: '<img onerror={{action "foo"}}>',
+    },
   ],
 
   bad: [
diff --git a/test/unit/rules/lint-no-obsolete-elements-test.js b/test/unit/rules/lint-no-obsolete-elements-test.js
new file mode 100644
index 00000000..f7b38e29
--- /dev/null
+++ b/test/unit/rules/lint-no-obsolete-elements-test.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+const rule = require('../../../lib/rules/lint-no-obsolete-elements');
+
+const ERROR_MESSAGE_OBSOLETE_ELEMENT = rule.ERROR_MESSAGE_OBSOLETE_ELEMENT;
+const OBSOLETE_ELEMENTS = rule.OBSOLETE_ELEMENTS;
+
+generateRuleTests({
+  name: 'no-obsolete-elements',
+
+  config: true,
+
+  good: [
+    '<div></div>',
+    `{{#let (component 'whatever-here') as |plaintext|}}
+      <plaintext />
+    {{/let}}`,
+  ],
+
+  bad: OBSOLETE_ELEMENTS.map(element => {
+    return {
+      template: `<${element}></${element}>`,
+      result: {
+        message: ERROR_MESSAGE_OBSOLETE_ELEMENT(element),
+        moduleId: 'layout.hbs',
+        source: `<${element}></${element}>`,
+        line: 1,
+        column: 0,
+      },
+    };
+  }),
+});