erblint-github 0.0.8 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -1
- data/lib/erblint-github/linters/github/accessibility/avoid_generic_link_text_counter.rb +7 -2
- data/lib/erblint-github/linters/github/accessibility/disabled_attribute_counter.rb +45 -0
- data/lib/erblint-github/linters/github/accessibility/landmark_has_label_counter.rb +75 -0
- data/lib/erblint-github/linters/github/accessibility/link_has_href_counter.rb +45 -0
- data/lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb +55 -0
- data/lib/erblint-github/linters/github/accessibility/svg_has_accessible_text_counter.rb +60 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e7ce2b605ec0f31cb56b3e74e9f2eb4e10a11a8ab07d960596209b7d9674427
|
4
|
+
data.tar.gz: 0a74bf6def4d4c0b34c3f8bba3af39f7b3207fb87a72e08cf9143f3d42a1f577
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04e924e98c1fe68f5b3d56d8f0cf9a4c0083d70d55192661a1c93dcb457b870df4ab5841c8376c2afc8c7b3c86be87266df513e22cecfe5f33b230d450d4f5a4
|
7
|
+
data.tar.gz: f266202201dc8de5a3bd6089ed7b5562962b130ec5643775c3a910a242086a13280e4e449ccf26f3c0f9e5c1874693c8ce3971dff965117079ecd568c3c11e33
|
data/README.md
CHANGED
@@ -27,10 +27,18 @@ linters:
|
|
27
27
|
enabled: true
|
28
28
|
GitHub::Accessibility::AvoidGenericLinkTextCounter:
|
29
29
|
enabled: true
|
30
|
+
GitHub::Accessibility::DisabledAttributeCounter:
|
31
|
+
enabled: true
|
30
32
|
GitHub::Accessibility::IframeHasTitle:
|
31
33
|
enabled: true
|
32
34
|
GitHub::Accessibility::ImageHasAlt:
|
33
35
|
enabled: true
|
36
|
+
GitHub::Accessibility::LandmarkHasLabelCounter:
|
37
|
+
enabled: true
|
38
|
+
GitHub::Accessibility::LinkHasHrefCounter:
|
39
|
+
enabled: true
|
40
|
+
GitHub::Accessibility::NestedInteractiveElementsCounter:
|
41
|
+
enabled: true
|
34
42
|
GitHub::Accessibility::NoAriaLabelMisuseCounter:
|
35
43
|
enabled: true
|
36
44
|
GitHub::Accessibility::NoPositiveTabIndex:
|
@@ -39,18 +47,25 @@ linters:
|
|
39
47
|
enabled: true
|
40
48
|
GitHub::Accessibility::NoTitleAttributeCounter:
|
41
49
|
enabled: true
|
50
|
+
GitHub::Accessibility::SvgHasAccessibleTextCounter:
|
51
|
+
enabled: true
|
42
52
|
```
|
43
53
|
|
44
54
|
## Rules
|
45
55
|
|
46
56
|
- [GitHub::Accessibility::AvoidBothDisabledAndAriaDisabled](./docs/rules/accessibility/avoid-both-disabled-and-aria-disabled.md)
|
47
57
|
- [GitHub::Accessibility::AvoidGenericLinkTextCounter](./docs/rules/accessibility/avoid-generic-link-text-counter.md)
|
58
|
+
- [GitHub::Accessibility::DisabledAttributeCounter](./docs/rules/accessibility/disabled-attribute-counter-test)
|
48
59
|
- [GitHub::Accessibility::IframeHasTitle](./docs/rules/accessibility/iframe-has-title.md)
|
60
|
+
- [GitHub::Accessibility::LandmarkHasLabelCounter](./docs/rules/accessibility/landmark-has-label-counter.md)
|
49
61
|
- [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md)
|
62
|
+
- [GitHub::Accessibility::LinkHasHrefCounter](./docs/rules/accessibility/link-has-href-counter.md)
|
63
|
+
- [GitHub::Accessibility::NestedInteractiveElementsCounter](./docs/rules/accessibility/nested-interactive-elements-counter.md)
|
50
64
|
- [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md)
|
51
65
|
- [GitHub::Accessibility::NoPositiveTabIndex](./docs/rules/accessibility/no-positive-tab-index.md)
|
52
66
|
- [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md)
|
53
67
|
- [GitHub::Accessibility::NoTitleAttributeCounter](./docs/rules/accessibility/no-title-attribute-counter.md)
|
68
|
+
- [GitHub::Accessibility::SvgHasAccessibleTextCounter](./docs/rules/accessibility/svg-has-accessible-text-counter.md)
|
54
69
|
|
55
70
|
## Testing
|
56
71
|
|
@@ -66,4 +81,4 @@ If you use VS Code, we highly encourage [ERB Linter extension](https://marketpla
|
|
66
81
|
## Note
|
67
82
|
|
68
83
|
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,
|
69
|
-
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.
|
84
|
+
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.
|
@@ -112,12 +112,17 @@ module ERBLint
|
|
112
112
|
|
113
113
|
private
|
114
114
|
|
115
|
+
# Downcase and strip punctuation and extra whitespaces.
|
116
|
+
def stripped_text(text)
|
117
|
+
text.downcase.gsub(/\W+/, " ").strip
|
118
|
+
end
|
119
|
+
|
115
120
|
def banned_text?(text)
|
116
|
-
BANNED_GENERIC_TEXT.map(&:downcase).include?(text
|
121
|
+
BANNED_GENERIC_TEXT.map(&:downcase).include?(stripped_text(text))
|
117
122
|
end
|
118
123
|
|
119
124
|
def valid_accessible_name?(aria_label, text)
|
120
|
-
aria_label.
|
125
|
+
stripped_text(aria_label).include?(stripped_text(text))
|
121
126
|
end
|
122
127
|
|
123
128
|
def extract_ruby_node(source)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../custom_helpers"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module GitHub
|
8
|
+
module Accessibility
|
9
|
+
class DisabledAttributeCounter < Linter
|
10
|
+
include ERBLint::Linters::CustomHelpers
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
VALID_DISABLED_TAGS = %w[button input textarea option select fieldset optgroup task-lists].freeze
|
14
|
+
MESSAGE = "`disabled` is only valid on #{VALID_DISABLED_TAGS.join(', ')}."
|
15
|
+
|
16
|
+
def run(processed_source)
|
17
|
+
tags(processed_source).each do |tag|
|
18
|
+
next if tag.closing?
|
19
|
+
next if VALID_DISABLED_TAGS.include?(tag.name)
|
20
|
+
next if tag.attributes["disabled"].nil?
|
21
|
+
|
22
|
+
generate_offense(self.class, processed_source, tag)
|
23
|
+
end
|
24
|
+
|
25
|
+
counter_correct?(processed_source)
|
26
|
+
end
|
27
|
+
|
28
|
+
def autocorrect(processed_source, offense)
|
29
|
+
return unless offense.context
|
30
|
+
|
31
|
+
lambda do |corrector|
|
32
|
+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
|
33
|
+
# update the counter if exists
|
34
|
+
corrector.replace(offense.source_range, offense.context)
|
35
|
+
else
|
36
|
+
# add comment with counter if none
|
37
|
+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../custom_helpers"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module GitHub
|
8
|
+
module Accessibility
|
9
|
+
class LandmarkHasLabelCounter < Linter
|
10
|
+
include ERBLint::Linters::CustomHelpers
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
LANDMARK_ROLES = %w[complementary navigation region search].freeze
|
14
|
+
LANDMARK_TAGS = %w[aside nav section].freeze
|
15
|
+
MESSAGE = "Landmark elements should have an aria-label attribute, or aria-labelledby if a heading elements exists in the landmark."
|
16
|
+
ROLE_TAG_MAPPING = { "complementary" => "aside", "navigation" => "nav", "region" => "section" }.freeze
|
17
|
+
|
18
|
+
def get_additional_message(tag, roles)
|
19
|
+
role_matched = (roles & ROLE_TAG_MAPPING.keys).first
|
20
|
+
if role_matched
|
21
|
+
tag_matched = ROLE_TAG_MAPPING[role_matched]
|
22
|
+
|
23
|
+
if tag.name == tag_matched
|
24
|
+
"The <#{tag_matched}> element will automatically communicate a role of '#{role_matched}'. You can safely drop the role attribute."
|
25
|
+
else
|
26
|
+
replace_message = if tag.name == "div"
|
27
|
+
"If possible replace this tag with a <#{tag_matched}>."
|
28
|
+
else
|
29
|
+
"Wrapping this element in a <#{tag_matched}> and setting a label on it is reccomended."
|
30
|
+
end
|
31
|
+
|
32
|
+
"The <#{tag_matched}> element will automatically communicate a role of '#{role_matched}'. #{replace_message}"
|
33
|
+
end
|
34
|
+
elsif roles.include?("search") && tag.name != "form"
|
35
|
+
"The 'search' role works best when applied to a <form> element. If possible replace this tag with a <form>."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run(processed_source)
|
40
|
+
tags(processed_source).each do |tag|
|
41
|
+
next if tag.closing?
|
42
|
+
|
43
|
+
possible_roles = possible_attribute_values(tag, "role")
|
44
|
+
next unless LANDMARK_TAGS.include?(tag.name) && (possible_roles & LANDMARK_ROLES).empty?
|
45
|
+
next if tag.attributes["aria-label"]&.value&.present? || tag.attributes["aria-labelledby"]&.value&.present?
|
46
|
+
|
47
|
+
message = get_additional_message(tag, possible_roles)
|
48
|
+
if message
|
49
|
+
generate_offense(self.class, processed_source, tag, "#{MESSAGE}\n#{message}")
|
50
|
+
else
|
51
|
+
generate_offense(self.class, processed_source, tag)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
counter_correct?(processed_source)
|
56
|
+
end
|
57
|
+
|
58
|
+
def autocorrect(processed_source, offense)
|
59
|
+
return unless offense.context
|
60
|
+
|
61
|
+
lambda do |corrector|
|
62
|
+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
|
63
|
+
# update the counter if exists
|
64
|
+
corrector.replace(offense.source_range, offense.context)
|
65
|
+
else
|
66
|
+
# add comment with counter if none
|
67
|
+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../custom_helpers"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module GitHub
|
8
|
+
module Accessibility
|
9
|
+
class LinkHasHrefCounter < Linter
|
10
|
+
include ERBLint::Linters::CustomHelpers
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
MESSAGE = "Links should go somewhere, you probably want to use a `<button>` instead."
|
14
|
+
|
15
|
+
def run(processed_source)
|
16
|
+
tags(processed_source).each do |tag|
|
17
|
+
next if tag.name != "a"
|
18
|
+
next if tag.closing?
|
19
|
+
|
20
|
+
href = possible_attribute_values(tag, "href")
|
21
|
+
name = tag.attributes["name"]
|
22
|
+
generate_offense(self.class, processed_source, tag) if (!name && href.empty?) || href.include?("#")
|
23
|
+
end
|
24
|
+
|
25
|
+
counter_correct?(processed_source)
|
26
|
+
end
|
27
|
+
|
28
|
+
def autocorrect(processed_source, offense)
|
29
|
+
return unless offense.context
|
30
|
+
|
31
|
+
lambda do |corrector|
|
32
|
+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
|
33
|
+
# update the counter if exists
|
34
|
+
corrector.replace(offense.source_range, offense.context)
|
35
|
+
else
|
36
|
+
# add comment with counter if none
|
37
|
+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../custom_helpers"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module GitHub
|
8
|
+
module Accessibility
|
9
|
+
class NestedInteractiveElementsCounter < Linter
|
10
|
+
include ERBLint::Linters::CustomHelpers
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
INTERACTIVE_ELEMENTS = %w[button summary input select textarea a].freeze
|
14
|
+
MESSAGE = "Nesting interactive elements produces invalid HTML, and ssistive technologies, such as screen readers, might ignore or respond unexpectedly to such nested controls."
|
15
|
+
|
16
|
+
def run(processed_source)
|
17
|
+
last_interactive_element = nil
|
18
|
+
tags(processed_source).each do |tag|
|
19
|
+
next unless INTERACTIVE_ELEMENTS.include?(tag.name)
|
20
|
+
|
21
|
+
last_interactive_element = nil if last_interactive_element && tag.name == last_interactive_element.name && tag.closing?
|
22
|
+
next if tag.closing?
|
23
|
+
|
24
|
+
if last_interactive_element
|
25
|
+
next if last_interactive_element.name == "summary" && tag.name == "a"
|
26
|
+
next if tag.name == "input" && tag.attributes["type"]&.value == "hidden"
|
27
|
+
|
28
|
+
message = "Found <#{tag.name}> nested inside of <#{last_interactive_element.name}>.\n" + MESSAGE
|
29
|
+
generate_offense(self.class, processed_source, tag, message)
|
30
|
+
end
|
31
|
+
|
32
|
+
last_interactive_element = tag unless tag&.name == "input"
|
33
|
+
end
|
34
|
+
|
35
|
+
counter_correct?(processed_source)
|
36
|
+
end
|
37
|
+
|
38
|
+
def autocorrect(processed_source, offense)
|
39
|
+
return unless offense.context
|
40
|
+
|
41
|
+
lambda do |corrector|
|
42
|
+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
|
43
|
+
# update the counter if exists
|
44
|
+
corrector.replace(offense.source_range, offense.context)
|
45
|
+
else
|
46
|
+
# add comment with counter if none
|
47
|
+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../custom_helpers"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module GitHub
|
8
|
+
module Accessibility
|
9
|
+
class SvgHasAccessibleTextCounter < Linter
|
10
|
+
include ERBLint::Linters::CustomHelpers
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
MESSAGE = "`<svg>` must have accessible text. Set `aria-label`, or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden='true'.\nFor more info, see https://css-tricks.com/accessible-svgs/."
|
14
|
+
|
15
|
+
def run(processed_source)
|
16
|
+
current_svg = nil
|
17
|
+
has_accessible_label = false
|
18
|
+
|
19
|
+
tags(processed_source).each do |tag|
|
20
|
+
# Checks whether tag is a <title> nested in an <svg>
|
21
|
+
has_accessible_label = true if current_svg && tag.name == "title" && !tag.closing?
|
22
|
+
|
23
|
+
next if tag.name != "svg"
|
24
|
+
|
25
|
+
if tag.closing?
|
26
|
+
generate_offense(self.class, processed_source, current_svg) unless has_accessible_label
|
27
|
+
current_svg = nil
|
28
|
+
elsif possible_attribute_values(tag, "aria-hidden").join == "true"
|
29
|
+
has_accessible_label = true
|
30
|
+
current_svg = tag
|
31
|
+
else
|
32
|
+
current_svg = tag
|
33
|
+
aria_label = possible_attribute_values(tag, "aria-label").join
|
34
|
+
aria_labelledby = possible_attribute_values(tag, "aria-labelledby").join
|
35
|
+
|
36
|
+
has_accessible_label = aria_label.present? || aria_labelledby.present?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
counter_correct?(processed_source)
|
41
|
+
end
|
42
|
+
|
43
|
+
def autocorrect(processed_source, offense)
|
44
|
+
return unless offense.context
|
45
|
+
|
46
|
+
lambda do |corrector|
|
47
|
+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
|
48
|
+
# update the counter if exists
|
49
|
+
corrector.replace(offense.source_range, offense.context)
|
50
|
+
else
|
51
|
+
# add comment with counter if none
|
52
|
+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erblint-github
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: erb_lint
|
@@ -107,12 +107,17 @@ files:
|
|
107
107
|
- lib/erblint-github/linters/custom_helpers.rb
|
108
108
|
- lib/erblint-github/linters/github/accessibility/avoid_both_disabled_and_aria_disabled.rb
|
109
109
|
- lib/erblint-github/linters/github/accessibility/avoid_generic_link_text_counter.rb
|
110
|
+
- lib/erblint-github/linters/github/accessibility/disabled_attribute_counter.rb
|
110
111
|
- lib/erblint-github/linters/github/accessibility/iframe_has_title.rb
|
111
112
|
- lib/erblint-github/linters/github/accessibility/image_has_alt.rb
|
113
|
+
- lib/erblint-github/linters/github/accessibility/landmark_has_label_counter.rb
|
114
|
+
- lib/erblint-github/linters/github/accessibility/link_has_href_counter.rb
|
115
|
+
- lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb
|
112
116
|
- lib/erblint-github/linters/github/accessibility/no_aria_label_misuse_counter.rb
|
113
117
|
- lib/erblint-github/linters/github/accessibility/no_positive_tab_index.rb
|
114
118
|
- lib/erblint-github/linters/github/accessibility/no_redundant_image_alt.rb
|
115
119
|
- lib/erblint-github/linters/github/accessibility/no_title_attribute_counter.rb
|
120
|
+
- lib/erblint-github/linters/github/accessibility/svg_has_accessible_text_counter.rb
|
116
121
|
- lib/tasks/docs.rake
|
117
122
|
- lib/tasks/tests.rake
|
118
123
|
homepage: https://github.com/github/erblint-github
|
@@ -135,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
140
|
- !ruby/object:Gem::Version
|
136
141
|
version: '0'
|
137
142
|
requirements: []
|
138
|
-
rubygems_version: 3.2.
|
143
|
+
rubygems_version: 3.2.9
|
139
144
|
signing_key:
|
140
145
|
specification_version: 4
|
141
146
|
summary: erblint GitHub
|