Skip to content

Commit 190ad9c

Browse files
author
Adrián Bolonio
authored
Merge pull request #40 from github/accessibility/issues/1293/migrate-a11y-erblint-rules-a11y-svg-has-accessible-text
Migrate accessibility rule `a11y_svg_has_accessible_text` from dotcom to erblint-github
2 parents ba77a1d + 841df4c commit 190ad9c

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ linters:
4343
enabled: true
4444
GitHub::Accessibility::NoTitleAttributeCounter:
4545
enabled: true
46+
GitHub::Accessibility::SvgHasAccessibleTextCounter:
47+
enabled: true
4648
```
4749
4850
## Rules
@@ -57,6 +59,7 @@ linters:
5759
- [GitHub::Accessibility::NoPositiveTabIndex](./docs/rules/accessibility/no-positive-tab-index.md)
5860
- [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md)
5961
- [GitHub::Accessibility::NoTitleAttributeCounter](./docs/rules/accessibility/no-title-attribute-counter.md)
62+
- [GitHub::Accessibility::SvgHasAccessibleTextCounter](./docs/rules/accessibility/svg_has_accessible_text_counter.md)
6063
6164
## Testing
6265
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# SVG has accessible text counter
2+
3+
## Rule Details
4+
5+
`<svg>` must have accessible text. Set `aria-label`, or `aria-labelledby`, or nest a `<title>` element.
6+
However, if the `<svg>` is purely decorative, hide it with `aria-hidden='true'.
7+
8+
## Resources
9+
10+
- [Accessible SVGs](https://css-tricks.com/accessible-svgs/)
11+
12+
## Examples
13+
### **Incorrect** code for this rule 👎
14+
15+
```erb
16+
<!-- incorrect -->
17+
<svg height='100' width='100'>
18+
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/>
19+
</svg>
20+
```
21+
22+
### **Correct** code for this rule 👍
23+
24+
```erb
25+
<!-- correct -->
26+
<svg aria-label='A circle' height='100' width='100'>
27+
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/>
28+
</svg>
29+
30+
<!-- correct -->
31+
<svg aria-labelledby='test_id' height='100' width='100'>
32+
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/>
33+
</svg>
34+
35+
<!-- correct -->
36+
<svg height='100' width='100'>
37+
<title>A circle</title>
38+
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/>
39+
</svg>
40+
```
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../../custom_helpers"
4+
5+
module ERBLint
6+
module Linters
7+
module GitHub
8+
module Accessibility
9+
class SvgHasAccessibleTextCounter < Linter
10+
include ERBLint::Linters::CustomHelpers
11+
include LinterRegistry
12+
13+
MESSAGE = "`<svg>` must have accessible text. Set `aria-label`, or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden='true'.\nFor more info, see https://css-tricks.com/accessible-svgs/."
14+
15+
def run(processed_source)
16+
current_svg = nil
17+
has_accessible_label = false
18+
19+
tags(processed_source).each do |tag|
20+
# Checks whether tag is a <title> nested in an <svg>
21+
has_accessible_label = true if current_svg && tag.name == "title" && !tag.closing?
22+
23+
next if tag.name != "svg"
24+
25+
if tag.closing?
26+
generate_offense(self.class, processed_source, current_svg) unless has_accessible_label
27+
current_svg = nil
28+
elsif possible_attribute_values(tag, "aria-hidden").join == "true"
29+
has_accessible_label = true
30+
current_svg = tag
31+
else
32+
current_svg = tag
33+
aria_label = possible_attribute_values(tag, "aria-label").join
34+
aria_labelledby = possible_attribute_values(tag, "aria-labelledby").join
35+
36+
has_accessible_label = aria_label.present? || aria_labelledby.present?
37+
end
38+
end
39+
40+
counter_correct?(processed_source)
41+
end
42+
43+
def autocorrect(processed_source, offense)
44+
return unless offense.context
45+
46+
lambda do |corrector|
47+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
48+
# update the counter if exists
49+
corrector.replace(offense.source_range, offense.context)
50+
else
51+
# add comment with counter if none
52+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
53+
end
54+
end
55+
end
56+
end
57+
end
58+
end
59+
end
60+
end
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class SvgHasAccessibleTextCounter < LinterTestCase
6+
def linter_class
7+
ERBLint::Linters::GitHub::Accessibility::SvgHasAccessibleTextCounter
8+
end
9+
10+
def test_warns_if_svg_does_not_have_accesible_text
11+
@file = "<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"
12+
@linter.run(processed_source)
13+
assert_equal(2, @linter.offenses.count)
14+
error_messages = @linter.offenses.map(&:message).sort
15+
assert_match(/If you must, add <%# erblint:counter GitHub::Accessibility::SvgHasAccessibleTextCounter 1 %> to bypass this check./, error_messages.first)
16+
assert_match(/`<svg>` must have accessible text. Set `aria-label`, or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden='true'./, error_messages.last)
17+
end
18+
19+
def test_does_not_warn_if_svg_has_accesible_text
20+
@file = "<svg aria-label='A circle' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"
21+
@linter.run(processed_source)
22+
23+
assert_empty @linter.offenses
24+
25+
@file = "<svg aria-labelledby='test_id' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"
26+
@linter.run(processed_source)
27+
28+
assert_empty @linter.offenses
29+
30+
@file = "<svg height='100' width='100'><title>A circle</title><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"
31+
@linter.run(processed_source)
32+
33+
assert_empty @linter.offenses
34+
end
35+
36+
def test_does_not_warn_if_svg_has_accesible_text_and_has_correct_counter_comment
37+
@file = <<~ERB
38+
<%# erblint:counter GitHub::Accessibility::SvgHasAccessibleTextCounter 1 %>
39+
<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>
40+
ERB
41+
@linter.run(processed_source)
42+
43+
assert_equal 0, @linter.offenses.count
44+
end
45+
46+
def test_does_not_autocorrect_when_ignores_are_correct
47+
@file = <<~ERB
48+
<%# erblint:counter GitHub::Accessibility::SvgHasAccessibleTextCounter 1 %>
49+
<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>
50+
ERB
51+
52+
assert_equal @file, corrected_content
53+
end
54+
55+
def test_does_autocorrect_when_ignores_are_not_correct
56+
@file = <<~ERB
57+
<%# erblint:counter GitHub::Accessibility::SvgHasAccessibleTextCounter 3 %>
58+
<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>
59+
ERB
60+
refute_equal @file, corrected_content
61+
62+
expected_content = <<~ERB
63+
<%# erblint:counter GitHub::Accessibility::SvgHasAccessibleTextCounter 1 %>
64+
<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>
65+
ERB
66+
assert_equal expected_content, corrected_content
67+
end
68+
end

0 commit comments

Comments
 (0)