diff --git a/__tests__/src/rules/anchor-ambiguous-text-test.js b/__tests__/src/rules/anchor-ambiguous-text-test.js
index fbd52e9ce..7706bb44c 100644
--- a/__tests__/src/rules/anchor-ambiguous-text-test.js
+++ b/__tests__/src/rules/anchor-ambiguous-text-test.js
@@ -39,6 +39,7 @@ ruleTester.run('anchor-ambiguous-text', rule, {
{ code: '${here};' },
{ code: 'click here;' },
{ code: 'click here;' },
+ { code: ';' },
{
code: 'click here',
options: [{
@@ -49,6 +50,10 @@ ruleTester.run('anchor-ambiguous-text', rule, {
code: 'documentation;',
settings: { 'jsx-a11y': { components: { Link: 'a' } } },
},
+ {
+ code: ';',
+ settings: { 'jsx-a11y': { components: { Image: 'img' } } },
+ },
{
code: '${here};',
settings: { 'jsx-a11y': { components: { Link: 'a' } } },
@@ -80,12 +85,20 @@ ruleTester.run('anchor-ambiguous-text', rule, {
{ code: ' click here;', errors: [expectedError] },
{ code: 'more textlearn more;', errors: [expectedError] },
{ code: 'more textlearn more;', errors: [expectedError] },
+ { code: ';', errors: [expectedError] },
+ { code: 'click here;', errors: [expectedError] },
+ { code: 'click here;', errors: [expectedError] },
{ code: 'click here;', errors: [expectedError] },
{
code: 'here',
errors: [expectedError],
settings: { 'jsx-a11y': { components: { Link: 'a' } } },
},
+ {
+ code: '',
+ errors: [expectedError],
+ settings: { 'jsx-a11y': { components: { Image: 'img' } } },
+ },
{
code: 'a disallowed word',
errors: [expectedErrorGenerator(['a disallowed word'])],
diff --git a/__tests__/src/util/getAccessibleChildText-test.js b/__tests__/src/util/getAccessibleChildText-test.js
index 02a0b4e1d..6b67db4e0 100644
--- a/__tests__/src/util/getAccessibleChildText-test.js
+++ b/__tests__/src/util/getAccessibleChildText-test.js
@@ -35,6 +35,27 @@ describe('getAccessibleChildText', () => {
), elementType)).toBe('bar');
});
+ it('returns alt text for img child', () => {
+ expect(getAccessibleChildText(JSXElementMock(
+ 'a',
+ [],
+ [JSXElementMock('img', [
+ JSXAttributeMock('src', 'some/path'),
+ JSXAttributeMock('alt', 'a sensible label'),
+ ])],
+ ), elementType)).toBe('a sensible label');
+ });
+
+ it('returns blank when alt tag is used on arbitrary element', () => {
+ expect(getAccessibleChildText(JSXElementMock(
+ 'a',
+ [],
+ [JSXElementMock('span', [
+ JSXAttributeMock('alt', 'a sensible label'),
+ ])],
+ ), elementType)).toBe('');
+ });
+
it('returns literal value for JSXText child', () => {
expect(getAccessibleChildText(JSXElementMock(
'a',
diff --git a/docs/rules/anchor-ambiguous-text.md b/docs/rules/anchor-ambiguous-text.md
index ae7bbd45d..d9888a462 100644
--- a/docs/rules/anchor-ambiguous-text.md
+++ b/docs/rules/anchor-ambiguous-text.md
@@ -22,7 +22,13 @@ The `words` option allows users to modify the strings that can be checked for in
const DEFAULT_AMBIGUOUS_WORDS = ['click here', 'here', 'link', 'a link', 'learn more'];
```
-If an element has the `aria-label` property, its value is used instead of the inner text. Note that the rule still disallows ambiguous `aria-label`s. This rule also skips over elements with `aria-hidden="true"`.
+The logic to calculate the inner text of an anchor is as follows:
+
+- if an element has the `aria-label` property, its value is used instead of the inner text
+- if an element has `aria-hidden="true`, it is skipped over
+- if an element is `` or configured to be interpreted like one, its `alt` value is used as its inner text
+
+Note that this rule still disallows ambiguous `aria-label` or `alt` values.
Note that this rule is case-insensitive and trims whitespace. It only looks for **exact matches**.
@@ -46,6 +52,8 @@ Note that this rule is case-insensitive and trims whitespace. It only looks for
a linkmore textlearn more // skips over elements with aria-hidden=true
something // the aria-label here is inaccessible
+ // the alt tag is still ambiguous
+click here // the alt tag is only parsed on img
```
## Accessibility guidelines
diff --git a/src/util/getAccessibleChildText.js b/src/util/getAccessibleChildText.js
index 9763db1a8..b25fd6139 100644
--- a/src/util/getAccessibleChildText.js
+++ b/src/util/getAccessibleChildText.js
@@ -29,6 +29,10 @@ export default function getAccessibleChildText(node: JSXElement, elementType: (J
// early escape-hatch when aria-label is applied
if (ariaLabel) return standardizeSpaceAndCase(ariaLabel);
+ // early-return if alt prop exists and is an image
+ const altTag = getLiteralPropValue(getProp(node.openingElement.attributes, 'alt'));
+ if (elementType(node.openingElement) === 'img' && altTag) return standardizeSpaceAndCase(altTag);
+
// skip if aria-hidden is true
if (
isHiddenFromScreenReader(