Skip to content

Commit ccc7d23

Browse files
khiga8iansan5653
andauthored
Introduce a no-empty-alt-text rule (#85)
* Make sure that HTML that is inlined is supported * Add detail * Update the helpers to support multiple errors in one line * add test support * Add test cases * Update configg * Update export * Add new rule * add test cases and update docs * Update test matchers * Update src/rules/no-empty-string-alt.js Co-authored-by: Ian Sanders <[email protected]> * Update src/rules/no-empty-string-alt.js Co-authored-by: Ian Sanders <[email protected]> * Revert "Update src/rules/no-empty-string-alt.js" This reverts commit 5b17abf. Reverting because we are using this index. * Fix Regex syntax * Remove markdown syntax support * Update doc to remove markdown syntax * Add test case for multiple images in one line Related:#85 (comment) * Rename rule to no-empty-alt-text * Add missing bracket * Update README with the rule * Update docs/rules/GH003-no-empty-alt-text.md Co-authored-by: Ian Sanders <[email protected]> * Update src/rules/no-empty-alt-text.js Co-authored-by: Ian Sanders <[email protected]> --------- Co-authored-by: Ian Sanders <[email protected]>
1 parent c29f004 commit ccc7d23

7 files changed

+142
-3
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The following are custom rules defined in this plugin.
1414

1515
* [**GH001** _no-default-alt-text_](./docs/rules/GH001-no-default-alt-text.md)
1616
* [**GH002** _no-generic-link-text_](./docs/rules/GH002-no-generic-link-text.md)
17+
* [**GH002** _no-empty-alt-text_](./docs/rules/GH002-no-empty-alt-text.md)
1718

1819
See [`markdownlint` rules](https://github.com/DavidAnson/markdownlint#rules--aliases) for documentation on rules pulled in from `markdownlint`.
1920

docs/rules/GH003-no-empty-alt-text.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# GH003 No Empty Alt Text
2+
3+
## Rule details
4+
5+
⚠️ This rule is _off_ by default and is only applicable for GitHub rendered markdown.
6+
7+
Currently, all images on github.com are automatically wrapped in an anchor tag.
8+
9+
As a result, images that are intentionally marked as decorative (via `alt=""`) end up rendering as a link without an accessible name. This is confusing and inaccessible for assistive technology users.
10+
11+
This rule can be enabled to enforce that the alt attribute is always set to descriptive text.
12+
13+
This rule should be removed once this behavior is updated on GitHub's UI.
14+
15+
## Examples
16+
17+
### Incorrect 👎
18+
19+
```html
20+
<img src="cat.png" alt="">
21+
```
22+
23+
### Correct 👍
24+
25+
```html
26+
<img src="mona.png" alt="Mona Lisa, the Octocat">
27+
```

index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ const gitHubCustomRules = require("./src/rules/index").rules;
66

77
module.exports = [...gitHubCustomRules];
88

9+
const offByDefault = ["no-empty-alt-text"];
10+
911
for (const rule of gitHubCustomRules) {
10-
base[rule.names[1]] = true;
12+
const ruleName = rule.names[1];
13+
base[ruleName] = offByDefault.includes(ruleName) ? false : true;
1114
}
1215

1316
module.exports.init = function init(consumerConfig) {

src/rules/index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
module.exports = {
2-
rules: [require("./no-default-alt-text"), require("./no-generic-link-text")],
2+
rules: [
3+
require("./no-default-alt-text"),
4+
require("./no-generic-link-text"),
5+
require("./no-empty-alt-text"),
6+
],
37
};

src/rules/no-empty-alt-text.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module.exports = {
2+
names: ["GH003", "no-empty-alt-text"],
3+
description: "Please provide an alternative text for the image.",
4+
information: new URL(
5+
"https://github.com/github/markdownlint-github/blob/main/docs/rules/GH003-no-empty-alt-text.md",
6+
),
7+
tags: ["accessibility", "images"],
8+
function: function GH003(params, onError) {
9+
const htmlTagsWithImages = params.parsers.markdownit.tokens.filter(
10+
(token) => {
11+
return (
12+
(token.type === "html_block" && token.content.includes("<img")) ||
13+
(token.type === "inline" &&
14+
token.content.includes("<img") &&
15+
token.children.some((child) => child.type === "html_inline"))
16+
);
17+
},
18+
);
19+
20+
const htmlAltRegex = new RegExp(/alt=['"]['"]/, "gid");
21+
22+
for (const token of htmlTagsWithImages) {
23+
const lineRange = token.map;
24+
const lineNumber = token.lineNumber;
25+
const lines = params.lines.slice(lineRange[0], lineRange[1]);
26+
27+
for (const [i, line] of lines.entries()) {
28+
const matches = line.matchAll(htmlAltRegex);
29+
for (const match of matches) {
30+
const matchingContent = match[0];
31+
const startIndex = match.indices[0][0];
32+
onError({
33+
lineNumber: lineNumber + i,
34+
range: [startIndex + 1, matchingContent.length],
35+
});
36+
}
37+
}
38+
}
39+
},
40+
};

test/no-empty-alt-text.test.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const noEmptyStringAltRule = require("../src/rules/no-empty-alt-text");
2+
const runTest = require("./utils/run-test").runTest;
3+
4+
describe("GH003: No Empty Alt Text", () => {
5+
describe("successes", () => {
6+
test("html image", async () => {
7+
const strings = [
8+
'<img alt="A helpful description" src="https://user-images.githubusercontent.com/abcdef.png">',
9+
"`<img alt='' src='image.png'>`", // code block
10+
];
11+
12+
const results = await runTest(strings, noEmptyStringAltRule);
13+
expect(results).toHaveLength(0);
14+
});
15+
});
16+
describe("failures", () => {
17+
test("HTML example", async () => {
18+
const strings = [
19+
'<img alt="" src="https://user-images.githubusercontent.com/abcdef.png">',
20+
"<img alt='' src='https://user-images.githubusercontent.com/abcdef.png'>",
21+
'<img src="cat.png" alt="" /> <img src="dog.png" alt="" />',
22+
];
23+
24+
const results = await runTest(strings, noEmptyStringAltRule);
25+
26+
const failedRules = results
27+
.map((result) => result.ruleNames)
28+
.flat()
29+
.filter((name) => !name.includes("GH"));
30+
31+
expect(failedRules).toHaveLength(4);
32+
for (const rule of failedRules) {
33+
expect(rule).toBe("no-empty-alt-text");
34+
}
35+
});
36+
37+
test("error message", async () => {
38+
const strings = [
39+
'<img alt="" src="https://user-images.githubusercontent.com/abcdef.png">',
40+
'<img src="cat.png" alt="" /> <img src="dog.png" alt="" />',
41+
];
42+
43+
const results = await runTest(strings, noEmptyStringAltRule);
44+
45+
expect(results[0].ruleDescription).toMatch(
46+
"Please provide an alternative text for the image.",
47+
);
48+
expect(results[0].errorRange).toEqual([6, 6]);
49+
50+
expect(results[1].ruleDescription).toMatch(
51+
"Please provide an alternative text for the image.",
52+
);
53+
expect(results[1].errorRange).toEqual([20, 6]);
54+
expect(results[2].ruleDescription).toMatch(
55+
"Please provide an alternative text for the image.",
56+
);
57+
expect(results[2].errorRange).toEqual([49, 6]);
58+
});
59+
});
60+
});

test/usage.test.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ describe("usage", () => {
44
describe("default export", () => {
55
test("custom rules on default export", () => {
66
const rules = githubMarkdownLint;
7-
expect(rules).toHaveLength(2);
7+
expect(rules).toHaveLength(3);
8+
89
expect(rules[0].names).toEqual(["GH001", "no-default-alt-text"]);
10+
expect(rules[1].names).toEqual(["GH002", "no-generic-link-text"]);
11+
expect(rules[2].names).toEqual(["GH003", "no-empty-alt-text"]);
912
});
1013
});
1114
describe("init method", () => {
@@ -17,6 +20,7 @@ describe("usage", () => {
1720
"no-space-in-links": false,
1821
"single-h1": true,
1922
"no-emphasis-as-header": true,
23+
"no-empty-alt-text": false,
2024
"heading-increment": true,
2125
"no-generic-link-text": true,
2226
"ul-style": {

0 commit comments

Comments
 (0)