From 7059faf5d17361f88cd7de7a10e9d5036f3240ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Bolonio?= Date: Wed, 27 Jul 2022 12:03:47 +0000 Subject: [PATCH] Add new linter rule: LandmarkHasLabelCounter --- README.md | 3 + .../landmark-has-label-counter.md | 28 +++++++ .../landmark_has_label_counter.rb | 75 +++++++++++++++++++ .../landmark_has_label_counter_test.rb | 75 +++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 docs/rules/accessibility/landmark-has-label-counter.md create mode 100644 lib/erblint-github/linters/github/accessibility/landmark_has_label_counter.rb create mode 100644 test/linters/accessibility/landmark_has_label_counter_test.rb diff --git a/README.md b/README.md index 65c9256..6becfad 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ linters: enabled: true GitHub::Accessibility::ImageHasAlt: enabled: true + GitHub::Accessibility::LandmarkHasLabelCounter: + enabled: true GitHub::Accessibility::LinkHasHrefCounter: enabled: true GitHub::Accessibility::NoAriaLabelMisuseCounter: @@ -53,6 +55,7 @@ linters: - [GitHub::Accessibility::AvoidGenericLinkTextCounter](./docs/rules/accessibility/avoid-generic-link-text-counter.md) - [GitHub::Accessibility::DisabledAttributeCounter](./docs/rules/accessibility/disabled-attribute-counter-test) - [GitHub::Accessibility::IframeHasTitle](./docs/rules/accessibility/iframe-has-title.md) +- [GitHub::Accessibility::LandmarkHasLabelCounter](./docs/rules/accessibility/landmark-has-label-counter.md) - [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md) - [GitHub::Accessibility::LinkHasHrefCounter](./docs/rules/accessibility/link_has_href-counter.md) - [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md) diff --git a/docs/rules/accessibility/landmark-has-label-counter.md b/docs/rules/accessibility/landmark-has-label-counter.md new file mode 100644 index 0000000..46f6011 --- /dev/null +++ b/docs/rules/accessibility/landmark-has-label-counter.md @@ -0,0 +1,28 @@ +# Landmark Has Label Counter + +## Rule Details + +Landmark elements should have an `aria-label` attribute, or `aria-labelledby` if a heading elements exists in the landmark. + +## Resources + +- [ARIA Landmarks Example](https://www.w3.org/WAI/ARIA/apg/example-index/landmarks/index.html) + +## Examples +### **Incorrect** code for this rule 👎 + +```erb + +
+

This is a text

+
+``` + +### **Correct** code for this rule 👍 + +```erb + +
+

This is a text

+
+``` diff --git a/lib/erblint-github/linters/github/accessibility/landmark_has_label_counter.rb b/lib/erblint-github/linters/github/accessibility/landmark_has_label_counter.rb new file mode 100644 index 0000000..b67ca22 --- /dev/null +++ b/lib/erblint-github/linters/github/accessibility/landmark_has_label_counter.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require_relative "../../custom_helpers" + +module ERBLint + module Linters + module GitHub + module Accessibility + class LandmarkHasLabelCounter < Linter + include ERBLint::Linters::CustomHelpers + include LinterRegistry + + LANDMARK_ROLES = %w[complementary navigation region search].freeze + LANDMARK_TAGS = %w[aside nav section].freeze + MESSAGE = "Landmark elements should have an aria-label attribute, or aria-labelledby if a heading elements exists in the landmark." + ROLE_TAG_MAPPING = { "complementary" => "aside", "navigation" => "nav", "region" => "section" }.freeze + + def get_additional_message(tag, roles) + role_matched = (roles & ROLE_TAG_MAPPING.keys).first + if role_matched + tag_matched = ROLE_TAG_MAPPING[role_matched] + + if tag.name == tag_matched + "The <#{tag_matched}> element will automatically communicate a role of '#{role_matched}'. You can safely drop the role attribute." + else + replace_message = if tag.name == "div" + "If possible replace this tag with a <#{tag_matched}>." + else + "Wrapping this element in a <#{tag_matched}> and setting a label on it is reccomended." + end + + "The <#{tag_matched}> element will automatically communicate a role of '#{role_matched}'. #{replace_message}" + end + elsif roles.include?("search") && tag.name != "form" + "The 'search' role works best when applied to a
element. If possible replace this tag with a ." + end + end + + def run(processed_source) + tags(processed_source).each do |tag| + next if tag.closing? + + possible_roles = possible_attribute_values(tag, "role") + next unless LANDMARK_TAGS.include?(tag.name) && (possible_roles & LANDMARK_ROLES).empty? + next if tag.attributes["aria-label"]&.value&.present? || tag.attributes["aria-labelledby"]&.value&.present? + + message = get_additional_message(tag, possible_roles) + if message + generate_offense(self.class, processed_source, tag, "#{MESSAGE}\n#{message}") + else + generate_offense(self.class, processed_source, tag) + end + end + + counter_correct?(processed_source) + end + + def autocorrect(processed_source, offense) + return unless offense.context + + lambda do |corrector| + if processed_source.file_content.include?("erblint:counter #{simple_class_name}") + # update the counter if exists + corrector.replace(offense.source_range, offense.context) + else + # add comment with counter if none + corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n") + end + end + end + end + end + end + end +end diff --git a/test/linters/accessibility/landmark_has_label_counter_test.rb b/test/linters/accessibility/landmark_has_label_counter_test.rb new file mode 100644 index 0000000..3fe56e9 --- /dev/null +++ b/test/linters/accessibility/landmark_has_label_counter_test.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "test_helper" + +class LandmarkHasLabelCounter < LinterTestCase + def linter_class + ERBLint::Linters::GitHub::Accessibility::LandmarkHasLabelCounter + end + + def test_warns_if_landmark_has_no_label + @file = <<~ERB +
+

This is a text

+
+ ERB + @linter.run(processed_source) + + assert_equal(2, @linter.offenses.count) + error_messages = @linter.offenses.map(&:message).sort + assert_match(/If you must, add <%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %> to bypass this check./, error_messages.first) + assert_match(/Landmark elements should have an aria-label attribute, or aria-labelledby if a heading elements exists in the landmark./, error_messages.last) + end + + def test_does_not_warn_if_landmark_has_label + @file = <<~ERB +
+

This is a text

+
+ ERB + @linter.run(processed_source) + + assert_empty @linter.offenses + end + + def test_does_not_warn_if_landmark_has_label_and_has_correct_counter_comment + @file = <<~ERB + <%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %> +
+

This is a text

+
+ ERB + @linter.run(processed_source) + + assert_equal 0, @linter.offenses.count + end + + def test_does_not_autocorrect_when_ignores_are_correct + @file = <<~ERB + <%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %> +
+

This is a text

+
+ ERB + + assert_equal @file, corrected_content + end + + def test_does_autocorrect_when_ignores_are_not_correct + @file = <<~ERB + <%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 3 %> +
+

This is a text

+
+ ERB + refute_equal @file, corrected_content + + expected_content = <<~ERB + <%# erblint:counter GitHub::Accessibility::LandmarkHasLabelCounter 1 %> +
+

This is a text

+
+ ERB + assert_equal expected_content, corrected_content + end +end