diff --git a/README.md b/README.md index f74d8b6..7adb85a 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,17 @@ require "erblint-github/linters" linters: GitHub::Accessibility::ImageHasAlt: enabled: true + GitHub::Accessibility::NoAriaLabelMisuse: + enabled: true GitHub::Accessibility::NoRedundantImageAlt: enabled: true ``` ### Rules +- [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/no-aria-label-misuse.md) +- [GitHub::Accessibility::NoAriaLabelMisuse](./docs/rules/accessibility/image-has-alt.md) - [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md) -- [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md) ## Testing diff --git a/docs/rules/accessibility/no-aria-label-misuse.md b/docs/rules/accessibility/no-aria-label-misuse.md new file mode 100644 index 0000000..a74224c --- /dev/null +++ b/docs/rules/accessibility/no-aria-label-misuse.md @@ -0,0 +1,45 @@ +# No aria label misuse + +## Rule Details + +This rule aims to minimize misuse of the `aria-label` and `aria-labelledby` attributes because the usage of these attributes is only guaranteed on interactive elements and a subset of ARIA roles. W3C provides [a list of ARIA roles which cannot be named](https://w3c.github.io/aria/#namefromprohibited) which is used as a basis for this linter. + +There are conflicting resources on what elements should support these naming attributes. For now, this rule will operate under a relatively simple heuristic aimed to minimize false positives, but has room for future improvements. + +Learn more at [W3C Name Calcluation](https://w3c.github.io/aria/#namecalculation). + +Also check out the following resources: +- [w3c/aria Consider prohibiting author naming certain roles #833](https://github.com/w3c/aria/issues/833) +- [Not so short note on aria-label usage - Big Table Edition](https://html5accessibility.com/stuff/2020/11/07/not-so-short-note-on-aria-label-usage-big-table-edition/) + +👎 Examples of **incorrect** code for this rule: + +```erb +Hello +``` + +```erb +
Goodbye
+``` + +```erb +

Page title

+``` + +👍 Examples of **correct** code for this rule: + +```erb +Hello +``` + +```erb +
Goodbye
+``` + +```erb +

Page title

+``` + +```erb +
+``` diff --git a/lib/erblint-github/linters/github/accessibility/no_aria_label_misuse.rb b/lib/erblint-github/linters/github/accessibility/no_aria_label_misuse.rb new file mode 100644 index 0000000..b8533fe --- /dev/null +++ b/lib/erblint-github/linters/github/accessibility/no_aria_label_misuse.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative "../../custom_helpers" + +module ERBLint + module Linters + module GitHub + module Accessibility + class NoAriaLabelMisuse < Linter + include ERBLint::Linters::CustomHelpers + include LinterRegistry + + GENERIC_ELEMENTS = %w[span div].freeze + NAME_RESTRICTED_ELEMENTS = %w[h1 h2 h3 h4 h5 h6 strong i p b code].freeze + + # https://w3c.github.io/aria/#namefromprohibited + ROLES_WHICH_CANNOT_BE_NAMED = %w[caption code definition deletion emphasis insertion mark none paragraph presentation strong subscript suggestion superscript term time].freeze + + MESSAGE = "[aria-label] and [aria-labelledby] usage are only reliably supported on interactive elements and a subset of ARIA roles" + + def run(processed_source) + tags(processed_source).each do |tag| + next if tag.closing? + next unless possible_attribute_values(tag, "aria-label").present? || possible_attribute_values(tag, "aria-labelledby").present? + + if NAME_RESTRICTED_ELEMENTS.include?(tag.name) + generate_offense(self.class, processed_source, tag) + elsif GENERIC_ELEMENTS.include?(tag.name) + role = possible_attribute_values(tag, "role") + if role.present? + generate_offense(self.class, processed_source, tag) if ROLES_WHICH_CANNOT_BE_NAMED.include?(role.join) + else + generate_offense(self.class, processed_source, tag) + end + end + end + rule_disabled?(processed_source) + end + end + end + end + end +end diff --git a/test/linters/accessibility/no_aria_label_misuse_test.rb b/test/linters/accessibility/no_aria_label_misuse_test.rb new file mode 100644 index 0000000..9a5135a --- /dev/null +++ b/test/linters/accessibility/no_aria_label_misuse_test.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "test_helper" + +class NoAriaLabelMisuseTest < LinterTestCase + def linter_class + ERBLint::Linters::GitHub::Accessibility::NoAriaLabelMisuse + end + + def example_invalid_case + <<~HTML + This is a span + HTML + end + + def test_warns_if_banned_elements_have_aria_label + @file = <<~HTML +

+

+

+

+
+
+ + +

+ + + HTML + + @linter.run(processed_source) + assert_equal @linter.offenses.length, 11 + end + + def test_warns_if_generic_elements_have_aria_label_and_no_role + @file = <<~HTML + This is a span +
This is a div
+ HTML + + @linter.run(processed_source) + assert_equal 2, @linter.offenses.length + end + + def test_warns_if_generic_elements_have_aria_label_and_prohibited_role + @file = <<~HTML + This is a span +
This is a div
+ This is a span +
This is a div
+ This is a span +
This is a div
+ This is a span +
This is a div
+ This is a span +
This is a div
+ This is a span +
This is a div
+ This is a span +
This is a div
+ HTML + + @linter.run(processed_source) + assert_equal 14, @linter.offenses.length + end + + def test_does_not_warn_if_generic_elements_have_aria_label_and_allowed_role + @file = <<~HTML +
+
+
+ HTML + + @linter.run(processed_source) + assert_empty @linter.offenses + end +end