From f5c36f0626726375a27254284296ca10f1526651 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Wed, 25 Jan 2023 19:48:09 -0500 Subject: [PATCH 1/5] rule: no-aria-hidden-on-focusable-elements --- README.md | 3 ++ .../no-aria-hidden-on-focusable-counter.md | 33 ++++++++++++++ lib/erblint-github/linters/custom_helpers.rb | 11 +++++ .../nested_interactive_elements_counter.rb | 1 - .../no_aria_hidden_on_focusable_counter.rb | 41 +++++++++++++++++ ...o_aria_hidden_on_focusable_counter_test.rb | 44 +++++++++++++++++++ 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md create mode 100644 lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb create mode 100644 test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb diff --git a/README.md b/README.md index abfd49b..70528a8 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ linters: enabled: true GitHub::Accessibility::NestedInteractiveElementsCounter: enabled: true + GitHub::Accessibility::NoAriaHiddenOnFocusableCounter: + enabled: true GitHub::Accessibility::NoAriaLabelMisuseCounter: enabled: true GitHub::Accessibility::NoPositiveTabIndexCounter: @@ -61,6 +63,7 @@ linters: - [GitHub::Accessibility::NestedInteractiveElementsCounter](./docs/rules/accessibility/nested-interactive-elements-counter.md) - [GitHub::Accessibility::IframeHasTitleCounter](./docs/rules/accessibility/iframe-has-title-counter.md) - [GitHub::Accessibility::ImageHasAltCounter](./docs/rules/accessibility/image-has-alt-counter.md) +- [GitHub::Accessibility::NoAriaHiddenOnFocusableCounter](./docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md) - [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md) - [GitHub::Accessibility::NoPositiveTabIndexCounter](./docs/rules/accessibility/no-positive-tab-index-counter.md) - [GitHub::Accessibility::NoRedundantImageAltCounter](./docs/rules/accessibility/no-redundant-image-alt-counter.md) diff --git a/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md b/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md new file mode 100644 index 0000000..7e92c09 --- /dev/null +++ b/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md @@ -0,0 +1,33 @@ +# No aria-hidden on focusable counter + +## Rule Details + +Elements that are focusable should not have `aria-hidden="true"` set. + +`aria-hidden="true"` hides an element from assistive technologies, but if the element is still reachable by keyboard, it can cause confusion amongst assistie technology users who may be able to reach the element, but not have access to the element or it's information. + +### Resources + +- [Accessibility insights: aria-hidden-focus](https://accessibilityinsights.io/info-examples/web/aria-hidden-focus/) + +## Examples + +### **Incorrect** code for this rule 👎 + +```erb + +``` + +```erb + +``` + +### **Correct** code for this rule 👍 + +```erb + +``` + +```erb + +``` diff --git a/lib/erblint-github/linters/custom_helpers.rb b/lib/erblint-github/linters/custom_helpers.rb index 86b5d8a..1dbbc90 100644 --- a/lib/erblint-github/linters/custom_helpers.rb +++ b/lib/erblint-github/linters/custom_helpers.rb @@ -6,6 +6,8 @@ module ERBLint module Linters module CustomHelpers + INTERACTIVE_ELEMENTS = %w[button summary input select textarea a].freeze + def rule_disabled?(processed_source) processed_source.parser.ast.descendants(:erb).each do |node| indicator_node, _, code_node, = *node @@ -89,6 +91,15 @@ def tags(processed_source) def simple_class_name self.class.name.gsub("ERBLint::Linters::", "") end + + def focusable?(tag) + tabindex = possible_attribute_values(tag, "tabindex") + if INTERACTIVE_ELEMENTS.include?(tag.name) + tabindex.empty? || tabindex.first.to_i >= 0 + else + tabindex.any? && tabindex.first.to_i >= 0 + end + end end end end diff --git a/lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb b/lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb index c800a89..9b1b5d3 100644 --- a/lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb +++ b/lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb @@ -10,7 +10,6 @@ class NestedInteractiveElementsCounter < Linter include ERBLint::Linters::CustomHelpers include LinterRegistry - INTERACTIVE_ELEMENTS = %w[button summary input select textarea a].freeze MESSAGE = "Nesting interactive elements produces invalid HTML, and ssistive technologies, such as screen readers, might ignore or respond unexpectedly to such nested controls." def run(processed_source) diff --git a/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb b/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb new file mode 100644 index 0000000..05c315a --- /dev/null +++ b/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require_relative "../../custom_helpers" + +module ERBLint + module Linters + module GitHub + module Accessibility + class NoAriaHiddenOnFocusableCounter < Linter + include ERBLint::Linters::CustomHelpers + include LinterRegistry + + MESSAGE = "Elements that are focusable should not have `aria-hidden='true' because it can cause confusion on the state of the element for assistive technolgoy users." + + def run(processed_source) + tags(processed_source).each do |tag| + aria_hidden = possible_attribute_values(tag, "aria-hidden") + generate_offense(self.class, processed_source, tag) if aria_hidden.include?("true") && focusable?(tag) + 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/no_aria_hidden_on_focusable_counter_test.rb b/test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb new file mode 100644 index 0000000..165626e --- /dev/null +++ b/test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "test_helper" + +class NoAriaHiddenOnFocusableCounterTest < LinterTestCase + def linter_class + ERBLint::Linters::GitHub::Accessibility::NoAriaHiddenOnFocusableCounter + end + + def test_does_not_warn_if_link_does_not_have_aria_hidden + @file = "GitHub" + @linter.run(processed_source) + + assert_empty @linter.offenses + end + + def test_does_not_warn_if_link_has_aria_hidden_false + @file = "GitHub" + @linter.run(processed_source) + + assert_empty @linter.offenses + end + + def test_warns_when_link_has_aria_hidden_true + @file = "" + @linter.run(processed_source) + + refute_empty @linter.offenses + end + + def test_does_not_warn_when_link_has_aria_hidden_true_and_is_not_focusable + @file = "" + @linter.run(processed_source) + + assert_empty @linter.offenses + end + + def test_warns_when_element_has_aria_hidden_true_and_is_tab_focusable + @file = "" + @linter.run(processed_source) + + refute_empty @linter.offenses + end +end From bd16afdbae37c5cb42141faae4bc1a5ccc2938f0 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Wed, 25 Jan 2023 20:27:04 -0500 Subject: [PATCH 2/5] fix typo --- .../github/accessibility/no_aria_hidden_on_focusable_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb b/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb index 05c315a..5bf83bd 100644 --- a/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb +++ b/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb @@ -10,7 +10,7 @@ class NoAriaHiddenOnFocusableCounter < Linter include ERBLint::Linters::CustomHelpers include LinterRegistry - MESSAGE = "Elements that are focusable should not have `aria-hidden='true' because it can cause confusion on the state of the element for assistive technolgoy users." + MESSAGE = "Elements that are focusable should not have `aria-hidden='true' because it can cause confusion on the state of the element for assistive technology users." def run(processed_source) tags(processed_source).each do |tag| From 16cc98660c93031ea1e3614f654ab44407003fbd Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Thu, 26 Jan 2023 08:42:29 -0500 Subject: [PATCH 3/5] Update no_aria_hidden_on_focusable_counter.rb --- .../github/accessibility/no_aria_hidden_on_focusable_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb b/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb index 5bf83bd..194fc78 100644 --- a/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb +++ b/lib/erblint-github/linters/github/accessibility/no_aria_hidden_on_focusable_counter.rb @@ -10,7 +10,7 @@ class NoAriaHiddenOnFocusableCounter < Linter include ERBLint::Linters::CustomHelpers include LinterRegistry - MESSAGE = "Elements that are focusable should not have `aria-hidden='true' because it can cause confusion on the state of the element for assistive technology users." + MESSAGE = "Elements that are focusable should not have `aria-hidden='true' because it will cause confusion for assistive technology users." def run(processed_source) tags(processed_source).each do |tag| From 4527dda9e06c57cf183f9e737a59ee246b0aa084 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Thu, 26 Jan 2023 08:57:38 -0500 Subject: [PATCH 4/5] update test and docs --- .../no-aria-hidden-on-focusable-counter.md | 6 ++++-- .../no_aria_hidden_on_focusable_counter_test.rb | 16 +++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md b/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md index 7e92c09..1d53513 100644 --- a/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md +++ b/docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md @@ -2,13 +2,15 @@ ## Rule Details -Elements that are focusable should not have `aria-hidden="true"` set. +Elements that are focusable should never have `aria-hidden="true"` set. -`aria-hidden="true"` hides an element from assistive technologies, but if the element is still reachable by keyboard, it can cause confusion amongst assistie technology users who may be able to reach the element, but not have access to the element or it's information. +`aria-hidden="true"` hides elements from assistive technologies. `aria-hidden="true"` should only be used to hide non-interactive content such as decorative elements or redundant text. If a focusable element has `aria-hidden="true"`, it can cause confusion amongst assistive technology users who may be able to reach the element but not receive information about it. ### Resources - [Accessibility insights: aria-hidden-focus](https://accessibilityinsights.io/info-examples/web/aria-hidden-focus/) +- [Deque: aria-hidden elements do not contain focusable elements](https://dequeuniversity.com/rules/axe/html/4.4/aria-hidden-focus) +- [W3: Element with aria-hidden has no content in sequential focus navigation](https://www.w3.org/WAI/standards-guidelines/act/rules/6cfa84/proposed/) ## Examples diff --git a/test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb b/test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb index 165626e..d8feff5 100644 --- a/test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb +++ b/test/linters/accessibility/no_aria_hidden_on_focusable_counter_test.rb @@ -21,18 +21,24 @@ def test_does_not_warn_if_link_has_aria_hidden_false assert_empty @linter.offenses end - def test_warns_when_link_has_aria_hidden_true - @file = "" + def test_does_not_warn_when_link_has_aria_hidden_true_and_is_not_focusable + @file = "" @linter.run(processed_source) + assert_empty @linter.offenses + end + + def test_warns_when_element_has_aria_hidden_true_and_not_tab_focusable + @file = "