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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d08f8a3f7dd601469444626fb19413c9e249f7b1a5cc7d2f71416fdb1c05d85
4
- data.tar.gz: b0e6eda49feea5b68a77c975160ea2a4690e74a7d5d4b44d3b1c2ff400487ab6
3
+ metadata.gz: 8e7ce2b605ec0f31cb56b3e74e9f2eb4e10a11a8ab07d960596209b7d9674427
4
+ data.tar.gz: 0a74bf6def4d4c0b34c3f8bba3af39f7b3207fb87a72e08cf9143f3d42a1f577
5
5
  SHA512:
6
- metadata.gz: 6e733cebcb4af6a1e3198b58db3df7fd01b8f0d57379da7c0b02f5c4c8f87f24f56462d7badcc2d4029a7e60508a7bc60c40f8312742093318b5c6013948e0ca
7
- data.tar.gz: 40868f41b146bc4c534266e270495bbebf08c77208ea9894df13105dab4311ddeb7a238881535337a123ec0b69d1f81fa8155b3f3c81b6181dbfd95066e5d0cb
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.downcase.gsub(/\W+/, " ").strip)
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.downcase.include?(text.downcase)
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.0.8
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-06-30 00:00:00.000000000 Z
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.32
143
+ rubygems_version: 3.2.9
139
144
  signing_key:
140
145
  specification_version: 4
141
146
  summary: erblint GitHub