Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ linters:
enabled: true
GitHub::Accessibility::LinkHasHrefCounter:
enabled: true
GitHub::Accessibility::NestedInteractiveElementsCounter:
enabled: true
GitHub::Accessibility::NoAriaLabelMisuseCounter:
enabled: true
GitHub::Accessibility::NoPositiveTabIndex:
Expand All @@ -54,12 +56,13 @@ linters:
- [GitHub::Accessibility::DisabledAttributeCounter](./docs/rules/accessibility/disabled-attribute-counter-test)
- [GitHub::Accessibility::IframeHasTitle](./docs/rules/accessibility/iframe-has-title.md)
- [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md)
- [GitHub::Accessibility::LinkHasHrefCounter](./docs/rules/accessibility/link_has_href-counter.md)
- [GitHub::Accessibility::LinkHasHrefCounter](./docs/rules/accessibility/link-has-href-counter.md)
- [GitHub::Accessibility::NestedInteractiveElementsCounter](./docs/rules/accessibility/nested-interactive-elements-counter.md)
- [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md)
- [GitHub::Accessibility::NoPositiveTabIndex](./docs/rules/accessibility/no-positive-tab-index.md)
- [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md)
- [GitHub::Accessibility::NoTitleAttributeCounter](./docs/rules/accessibility/no-title-attribute-counter.md)
- [GitHub::Accessibility::SvgHasAccessibleTextCounter](./docs/rules/accessibility/svg_has_accessible_text_counter.md)
- [GitHub::Accessibility::SvgHasAccessibleTextCounter](./docs/rules/accessibility/svg-has-accessible-text-counter.md)

## Testing

Expand All @@ -75,4 +78,4 @@ If you use VS Code, we highly encourage [ERB Linter extension](https://marketpla
## Note

This repo contains several accessibility-related linting rules to help surface accessibility issues that would otherwise go undetected until a later stage. Please note that due to the limitations of static code analysis,
these ERB accessibility checks are NOT enough for ensuring the accessibility of your app. This shouldn't be the only tool you use to catch accessibility issues and should be supplemented with other tools that can check the runtime browser DOM output, as well as processes like accessibility design reviews, manual audits, user testing, etc.
these ERB accessibility checks are NOT enough for ensuring the accessibility of your app. This shouldn't be the only tool you use to catch accessibility issues and should be supplemented with other tools that can check the runtime browser DOM output, as well as processes like accessibility design reviews, manual audits, user testing, etc.
27 changes: 27 additions & 0 deletions docs/rules/accessibility/nested-interactive-elements-counter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Nested Interactive Elements Counter

## Rule Details

Certain interactive controls such as `button`, `summary`, `input`, `select`, `textarea`, or `a` can't have interactive children. Nesting interactive elements produces invalid HTML, and ssistive technologies, such as screen readers, might ignore or respond unexpectedly to such nested controls.

## Resources

- [Deque University](https://dequeuniversity.com/rules/axe/4.2/nested-interactive)
- [Accessibility Insights](https://accessibilityinsights.io/info-examples/web/nested-interactive/)

## Examples
### **Incorrect** code for this rule 👎

```erb
<!-- incorrect -->
<button>
<a href='https://github.com/'>Go to GitHub</a>
</button>
```

### **Correct** code for this rule 👍

```erb
<!-- correct -->
<button>Confirm</button>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

require_relative "../../custom_helpers"

module ERBLint
module Linters
module GitHub
module Accessibility
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)
last_interactive_element = nil
tags(processed_source).each do |tag|
next unless INTERACTIVE_ELEMENTS.include?(tag.name)

last_interactive_element = nil if last_interactive_element && tag.name == last_interactive_element.name && tag.closing?
next if tag.closing?

if last_interactive_element
next if last_interactive_element.name == "summary" && tag.name == "a"
next if tag.name == "input" && tag.attributes["type"]&.value == "hidden"

message = "Found <#{tag.name}> nested inside of <#{last_interactive_element.name}>.\n" + MESSAGE
generate_offense(self.class, processed_source, tag, message)
end

last_interactive_element = tag unless tag&.name == "input"
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

require "test_helper"

class NestedInteractiveElementsCounter < LinterTestCase
def linter_class
ERBLint::Linters::GitHub::Accessibility::NestedInteractiveElementsCounter
end

def test_warns_if_there_are_nested_interactive_elements
@file = "<button><a href='https://github.com/'>Go to GitHub</a></button>"
@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::NestedInteractiveElementsCounter 1 %> to bypass this check./, error_messages.first)
assert_match(/Nesting interactive elements produces invalid HTML, and ssistive technologies, such as screen readers, might ignore or respond unexpectedly to such nested controls./, error_messages.last)
end

def test_does_not_warn_if_there_are_not_nested_interactive_elements
@file = "<button>Confirm</button>"
@linter.run(processed_source)

assert_empty @linter.offenses
end

def test_does_not_warn_if_there_are_not_nested_interactive_elements_and_has_correct_counter_comment
@file = <<~ERB
<%# erblint:counter GitHub::Accessibility::NestedInteractiveElementsCounter 1 %>
<button><a href='https://github.com/'>Go to GitHub</a></button>
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::NestedInteractiveElementsCounter 1 %>
<button><a href='https://github.com/'>Go to GitHub</a></button>
ERB

assert_equal @file, corrected_content
end

def test_does_autocorrect_when_ignores_are_not_correct
@file = <<~ERB
<%# erblint:counter GitHub::Accessibility::NestedInteractiveElementsCounter 3 %>
<button><a href='https://github.com/'>Go to GitHub</a></button>
ERB
refute_equal @file, corrected_content

expected_content = <<~ERB
<%# erblint:counter GitHub::Accessibility::NestedInteractiveElementsCounter 1 %>
<button><a href='https://github.com/'>Go to GitHub</a></button>
ERB
assert_equal expected_content, corrected_content
end
end