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