Skip to content

Commit d71329c

Browse files
author
Adrián Bolonio
authored
Merge pull request #43 from github/accessibility/issues/1293/migrate-a11y-erblint-rules-a11y-landmark-has-label
Migrate accessibility rule `a11y_landmark_has_label` from dotcom to erblint-github
2 parents 5c37828 + 7059faf commit d71329c

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ linters:
3333
enabled: true
3434
GitHub::Accessibility::ImageHasAlt:
3535
enabled: true
36+
GitHub::Accessibility::LandmarkHasLabelCounter:
37+
enabled: true
3638
GitHub::Accessibility::LinkHasHrefCounter:
3739
enabled: true
3840
GitHub::Accessibility::NestedInteractiveElementsCounter:
@@ -55,6 +57,7 @@ linters:
5557
- [GitHub::Accessibility::AvoidGenericLinkTextCounter](./docs/rules/accessibility/avoid-generic-link-text-counter.md)
5658
- [GitHub::Accessibility::DisabledAttributeCounter](./docs/rules/accessibility/disabled-attribute-counter-test)
5759
- [GitHub::Accessibility::IframeHasTitle](./docs/rules/accessibility/iframe-has-title.md)
60+
- [GitHub::Accessibility::LandmarkHasLabelCounter](./docs/rules/accessibility/landmark-has-label-counter.md)
5861
- [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md)
5962
- [GitHub::Accessibility::LinkHasHrefCounter](./docs/rules/accessibility/link-has-href-counter.md)
6063
- [GitHub::Accessibility::NestedInteractiveElementsCounter](./docs/rules/accessibility/nested-interactive-elements-counter.md)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Landmark Has Label Counter
2+
3+
## Rule Details
4+
5+
Landmark elements should have an `aria-label` attribute, or `aria-labelledby` if a heading elements exists in the landmark.
6+
7+
## Resources
8+
9+
- [ARIA Landmarks Example](https://www.w3.org/WAI/ARIA/apg/example-index/landmarks/index.html)
10+
11+
## Examples
12+
### **Incorrect** code for this rule 👎
13+
14+
```erb
15+
<!-- incorrect -->
16+
<section>
17+
<h1>This is a text</h1>
18+
</section>
19+
```
20+
21+
### **Correct** code for this rule 👍
22+
23+
```erb
24+
<!-- correct -->
25+
<section aria-labelledby="title_id"t>
26+
<h1 id="title_id">This is a text</h1>
27+
</section>
28+
```
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 LandmarkHasLabelCounter < Linter
10+
include ERBLint::Linters::CustomHelpers
11+
include LinterRegistry
12+
13+
LANDMARK_ROLES = %w[complementary navigation region search].freeze
14+
LANDMARK_TAGS = %w[aside nav section].freeze
15+
MESSAGE = "Landmark elements should have an aria-label attribute, or aria-labelledby if a heading elements exists in the landmark."
16+
ROLE_TAG_MAPPING = { "complementary" => "aside", "navigation" => "nav", "region" => "section" }.freeze
17+
18+
def get_additional_message(tag, roles)
19+
role_matched = (roles & ROLE_TAG_MAPPING.keys).first
20+
if role_matched
21+
tag_matched = ROLE_TAG_MAPPING[role_matched]
22+
23+
if tag.name == tag_matched
24+
"The <#{tag_matched}> element will automatically communicate a role of '#{role_matched}'. You can safely drop the role attribute."
25+
else
26+
replace_message = if tag.name == "div"
27+
"If possible replace this tag with a <#{tag_matched}>."
28+
else
29+
"Wrapping this element in a <#{tag_matched}> and setting a label on it is reccomended."
30+
end
31+
32+
"The <#{tag_matched}> element will automatically communicate a role of '#{role_matched}'. #{replace_message}"
33+
end
34+
elsif roles.include?("search") && tag.name != "form"
35+
"The 'search' role works best when applied to a <form> element. If possible replace this tag with a <form>."
36+
end
37+
end
38+
39+
def run(processed_source)
40+
tags(processed_source).each do |tag|
41+
next if tag.closing?
42+
43+
possible_roles = possible_attribute_values(tag, "role")
44+
next unless LANDMARK_TAGS.include?(tag.name) && (possible_roles & LANDMARK_ROLES).empty?
45+
next if tag.attributes["aria-label"]&.value&.present? || tag.attributes["aria-labelledby"]&.value&.present?
46+
47+
message = get_additional_message(tag, possible_roles)
48+
if message
49+
generate_offense(self.class, processed_source, tag, "#{MESSAGE}\n#{message}")
50+
else
51+
generate_offense(self.class, processed_source, tag)
52+
end
53+
end
54+
55+
counter_correct?(processed_source)
56+
end
57+
58+
def autocorrect(processed_source, offense)
59+
return unless offense.context
60+
61+
lambda do |corrector|
62+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
63+
# update the counter if exists
64+
corrector.replace(offense.source_range, offense.context)
65+
else
66+
# add comment with counter if none
67+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
68+
end
69+
end
70+
end
71+
end
72+
end
73+
end
74+
end
75+
end
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class LandmarkHasLabelCounter < LinterTestCase
6+
def linter_class
7+
ERBLint::Linters::GitHub::Accessibility::LandmarkHasLabelCounter
8+
end
9+
10+
def test_warns_if_landmark_has_no_label
11+
@file = <<~ERB
12+
<section>
13+
<h1>This is a text</h1>
14+
</section>
15+
ERB
16+
@linter.run(processed_source)
17+
18+
assert_equal(2, @linter.offenses.count)
19+
error_messages = @linter.offenses.map(&:message).sort
20+
assert_match(/If you must, add <%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %> to bypass this check./, error_messages.first)
21+
assert_match(/Landmark elements should have an aria-label attribute, or aria-labelledby if a heading elements exists in the landmark./, error_messages.last)
22+
end
23+
24+
def test_does_not_warn_if_landmark_has_label
25+
@file = <<~ERB
26+
<section aria-labelledby="title_id"t>
27+
<h1 id="title_id">This is a text</h1>
28+
</section>
29+
ERB
30+
@linter.run(processed_source)
31+
32+
assert_empty @linter.offenses
33+
end
34+
35+
def test_does_not_warn_if_landmark_has_label_and_has_correct_counter_comment
36+
@file = <<~ERB
37+
<%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %>
38+
<section>
39+
<h1>This is a text</h1>
40+
</section>
41+
ERB
42+
@linter.run(processed_source)
43+
44+
assert_equal 0, @linter.offenses.count
45+
end
46+
47+
def test_does_not_autocorrect_when_ignores_are_correct
48+
@file = <<~ERB
49+
<%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %>
50+
<section>
51+
<h1>This is a text</h1>
52+
</section>
53+
ERB
54+
55+
assert_equal @file, corrected_content
56+
end
57+
58+
def test_does_autocorrect_when_ignores_are_not_correct
59+
@file = <<~ERB
60+
<%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 3 %>
61+
<section>
62+
<h1>This is a text</h1>
63+
</section>
64+
ERB
65+
refute_equal @file, corrected_content
66+
67+
expected_content = <<~ERB
68+
<%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %>
69+
<section>
70+
<h1>This is a text</h1>
71+
</section>
72+
ERB
73+
assert_equal expected_content, corrected_content
74+
end
75+
end

0 commit comments

Comments
 (0)