Repository: ember-template-lint/ember-template-lint
Pull request #849 (2019-10-08 18:27:02)

Improve `require-valid-alt-text` rule to catch additional common failure scenarios
If merged, this PR updates the `require-valid-alt-text` rule to catch common failures as listed in https://www.w3.org/WAI/WCAG21/Techniques/failures/F30.html 

- catches number-only alt text
- adds a few new words to the redundant words list
- catches instances where the src value is equal to the alt value


Patch:
diff --git a/docs/rule/require-valid-alt-text.md b/docs/rule/require-valid-alt-text.md
index c76e5cce..1fff4179 100644
--- a/docs/rule/require-valid-alt-text.md
+++ b/docs/rule/require-valid-alt-text.md
@@ -16,6 +16,8 @@ If it's not a meaningful image, it should have an empty alt attribute value and
 
 `img` alt attribute does not contain the word image, picture, or photo. Screenreaders already announce `img` elements as an image. There is no need to use words such as *image*, *photo*, and/or *picture*.
 
+Numbers are not considered valid alt text, and this rule disallows using only numbers in alt text.
+
 
 This rule **forbids** the following:
 
@@ -24,6 +26,7 @@ This rule **forbids** the following:
 <img src="foo" alt="Photo of foo being weird." />
 <img src="bar" alt="Image of me at a bar!" />
 <img src="baz" alt="Picture of baz fixing a bug." />
+<img src="b52.jpg" alt="52" />
 ```
 
 This rule **allows** the following:
@@ -32,6 +35,7 @@ This rule **allows** the following:
 <img src="rwjblue.png" alt="A man standing in front of a room of people, giving a presentation about Ember.">
 <img src="bar" aria-hidden alt="Picture of me taking a photo of an image" /> // Will pass because it is hidden.
 <img src="baz" alt="Baz taking a {{photo}}" /> // This is valid since photo is a variable name.
+<img src="b52.jpg" alt="b52 bomber jet" />
 ```
 
 ### `<object>`
diff --git a/lib/rules/lint-require-valid-alt-text.js b/lib/rules/lint-require-valid-alt-text.js
index 5644f4b5..95c264ec 100644
--- a/lib/rules/lint-require-valid-alt-text.js
+++ b/lib/rules/lint-require-valid-alt-text.js
@@ -7,10 +7,10 @@ function hasAccessibleChild(node) {
   return AstNodeInfo.hasChildren(node);
 }
 
-const REDUNDANT_WORDS = ['image', 'photo', 'picture'];
+const REDUNDANT_WORDS = ['image', 'photo', 'picture', 'logo', 'spacer'];
 
-const errorMessage =
-  'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You don’t need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt attribute.';
+const ERROR_MESSAGE =
+  'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.';
 
 module.exports = class A11yLintAltText extends Rule {
   logNode({ node, message }) {
@@ -40,11 +40,18 @@ module.exports = class A11yLintAltText extends Rule {
 
         if (isImg) {
           const hasAltAttribute = AstNodeInfo.hasAttribute(node, 'alt');
+          const altValue = AstNodeInfo.elementAttributeValue(node, 'alt');
           const roleValue = AstNodeInfo.elementAttributeValue(node, 'role');
+          const srcValue = AstNodeInfo.elementAttributeValue(node, 'src');
 
           if (!hasAltAttribute) {
             this.logNode({
-              message: 'img tags must have an alt attribute',
+              message: 'All `<img>` tags must have an alt attribute',
+              node,
+            });
+          } else if (altValue === srcValue) {
+            this.logNode({
+              message: 'The alt text must not be the same as the image source',
               node,
             });
           } else {
@@ -73,23 +80,28 @@ module.exports = class A11yLintAltText extends Rule {
                 }
                 this.logNode({
                   message:
-                    'if the `alt` attribute is present and the value is an empty string, `role="presentation"` or `role="none"` must be present',
+                    'If the `alt` attribute is present and the value is an empty string, `role="presentation"` or `role="none"` must be present',
                   node,
                 });
                 return;
               }
 
               if (normalizedAltValue !== null) {
-                const existingWords = REDUNDANT_WORDS.filter(
-                  word => normalizedAltValue.indexOf(word) !== -1
-                );
-                if (existingWords.length) {
-                  this.log({
-                    message: errorMessage,
-                    line: node.loc && node.loc.start.line,
-                    column: node.loc && node.loc.start.column,
-                    source: this.sourceForNode(node),
+                if (normalizedAltValue.match(/^[0-9]+$/g)) {
+                  this.logNode({
+                    message: 'A number is not valid alt text',
+                    node,
                   });
+                } else {
+                  const existingWords = REDUNDANT_WORDS.filter(
+                    word => normalizedAltValue.indexOf(word) !== -1
+                  );
+                  if (existingWords.length) {
+                    this.logNode({
+                      message: ERROR_MESSAGE,
+                      node,
+                    });
+                  }
                 }
               }
             }
@@ -108,7 +120,7 @@ module.exports = class A11yLintAltText extends Rule {
           if (!hasValidAttributes) {
             this.logNode({
               message:
-                '<input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',
+                'All <input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',
               node,
             });
           }
@@ -146,3 +158,5 @@ module.exports = class A11yLintAltText extends Rule {
     };
   }
 };
+
+module.exports.ERROR_MESSAGE = ERROR_MESSAGE;


Test patch:
diff --git a/test/unit/rules/lint-require-valid-alt-text-test.js b/test/unit/rules/lint-require-valid-alt-text-test.js
index 31e9c370..c96946d7 100644
--- a/test/unit/rules/lint-require-valid-alt-text-test.js
+++ b/test/unit/rules/lint-require-valid-alt-text-test.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const generateRuleTests = require('../../helpers/rule-test-harness');
+const ERROR_MESSAGE = require('../../../lib/rules/lint-require-valid-alt-text').ERROR_MESSAGE;
 
 generateRuleTests({
   name: 'require-valid-alt-text',
@@ -49,7 +50,7 @@ generateRuleTests({
       template: '<img>',
 
       result: {
-        message: 'img tags must have an alt attribute',
+        message: 'All `<img>` tags must have an alt attribute',
         moduleId: 'layout.hbs',
         source: '<img>',
         line: 1,
@@ -60,7 +61,7 @@ generateRuleTests({
       template: '<img src="zoey.jpg">',
 
       result: {
-        message: 'img tags must have an alt attribute',
+        message: 'All `<img>` tags must have an alt attribute',
         moduleId: 'layout.hbs',
         source: '<img src="zoey.jpg">',
         line: 1,
@@ -72,7 +73,7 @@ generateRuleTests({
 
       result: {
         message:
-          'if the `alt` attribute is present and the value is an empty string, `role="presentation"` or `role="none"` must be present',
+          'If the `alt` attribute is present and the value is an empty string, `role="presentation"` or `role="none"` must be present',
         moduleId: 'layout.hbs',
         source: '<img alt="" src="zoey.jpg">',
         line: 1,
@@ -84,19 +85,29 @@ generateRuleTests({
 
       result: {
         message:
-          'if the `alt` attribute is present and the value is an empty string, `role="presentation"` or `role="none"` must be present',
+          'If the `alt` attribute is present and the value is an empty string, `role="presentation"` or `role="none"` must be present',
         moduleId: 'layout.hbs',
         source: '<img alt src="zoey.jpg">',
         line: 1,
         column: 0,
       },
     },
+    {
+      template: '<img alt="path/to/zoey.jpg" src="path/to/zoey.jpg">',
+      result: {
+        message: 'The alt text must not be the same as the image source',
+        moduleId: 'layout.hbs',
+        source: '<img alt="path/to/zoey.jpg" src="path/to/zoey.jpg">',
+        line: 1,
+        column: 0,
+      },
+    },
     {
       template: '<input type="image">',
 
       result: {
         message:
-          '<input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',
+          'All <input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',
         moduleId: 'layout.hbs',
         source: '<input type="image">',
         line: 1,
@@ -140,8 +151,7 @@ generateRuleTests({
       template: '<img alt="picture">',
 
       result: {
-        message:
-          'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You don’t need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt attribute.',
+        message: ERROR_MESSAGE,
         moduleId: 'layout.hbs',
         source: '<img alt="picture">',
         line: 1,
@@ -152,8 +162,7 @@ generateRuleTests({
       template: '<img alt="photo">',
 
       result: {
-        message:
-          'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You don’t need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt attribute.',
+        message: ERROR_MESSAGE,
         moduleId: 'layout.hbs',
         source: '<img alt="photo">',
         line: 1,
@@ -164,8 +173,7 @@ generateRuleTests({
       template: '<img alt="image">',
 
       result: {
-        message:
-          'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You don’t need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt attribute.',
+        message: ERROR_MESSAGE,
         moduleId: 'layout.hbs',
         source: '<img alt="image">',
         line: 1,
@@ -176,8 +184,7 @@ generateRuleTests({
       template: '<img alt="  IMAGE ">',
 
       result: {
-        message:
-          'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You don’t need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt attribute.',
+        message: ERROR_MESSAGE,
         moduleId: 'layout.hbs',
         source: '<img alt="  IMAGE ">',
         line: 1,
@@ -188,13 +195,23 @@ generateRuleTests({
       template: '<img alt="  IMAGE {{picture}} {{word}} ">',
 
       result: {
-        message:
-          'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You don’t need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt attribute.',
+        message: ERROR_MESSAGE,
         moduleId: 'layout.hbs',
         source: '<img alt="  IMAGE {{picture}} {{word}} ">',
         line: 1,
         column: 0,
       },
     },
+    {
+      template: '<img alt="52" src="b52.jpg">',
+
+      result: {
+        message: 'A number is not valid alt text',
+        moduleId: 'layout.hbs',
+        source: '<img alt="52" src="b52.jpg">',
+        line: 1,
+        column: 0,
+      },
+    },
   ],
 });