erblint-github 0.0.5 → 0.0.8

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: 48eba970607504df53f6b0f2ce5cff3b07051148eb1f4dca2fd78fa8287671a9
4
- data.tar.gz: 820da77f5a3ad624c32f4de22817e1c3d80ed755ddbe717368ef97e17e87e262
3
+ metadata.gz: 5d08f8a3f7dd601469444626fb19413c9e249f7b1a5cc7d2f71416fdb1c05d85
4
+ data.tar.gz: b0e6eda49feea5b68a77c975160ea2a4690e74a7d5d4b44d3b1c2ff400487ab6
5
5
  SHA512:
6
- metadata.gz: 34b69cd67df4ea14698098a1331bd787cc9c653cb616f8020c0767478427a41144d177706397eaedc7f7f3767b60758231a000ab45d3fef2dcd8ee07249b49ad
7
- data.tar.gz: d34ed13a0a85b146f4b2eff4e6fcbe00bbe7aef41d7168c6dadb2cc78a3fd49b42cc5dc92e6669cffb7b9bb81cf982495862fb46acb166a49dd63c9813d31d60
6
+ metadata.gz: 6e733cebcb4af6a1e3198b58db3df7fd01b8f0d57379da7c0b02f5c4c8f87f24f56462d7badcc2d4029a7e60508a7bc60c40f8312742093318b5c6013948e0ca
7
+ data.tar.gz: 40868f41b146bc4c534266e270495bbebf08c77208ea9894df13105dab4311ddeb7a238881535337a123ec0b69d1f81fa8155b3f3c81b6181dbfd95066e5d0cb
data/README.md CHANGED
@@ -25,6 +25,8 @@ require "erblint-github/linters"
25
25
  linters:
26
26
  GitHub::Accessibility::AvoidBothDisabledAndAriaDisabled:
27
27
  enabled: true
28
+ GitHub::Accessibility::AvoidGenericLinkTextCounter:
29
+ enabled: true
28
30
  GitHub::Accessibility::IframeHasTitle:
29
31
  enabled: true
30
32
  GitHub::Accessibility::ImageHasAlt:
@@ -42,6 +44,7 @@ linters:
42
44
  ## Rules
43
45
 
44
46
  - [GitHub::Accessibility::AvoidBothDisabledAndAriaDisabled](./docs/rules/accessibility/avoid-both-disabled-and-aria-disabled.md)
47
+ - [GitHub::Accessibility::AvoidGenericLinkTextCounter](./docs/rules/accessibility/avoid-generic-link-text-counter.md)
45
48
  - [GitHub::Accessibility::IframeHasTitle](./docs/rules/accessibility/iframe-has-title.md)
46
49
  - [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md)
47
50
  - [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md)
@@ -55,3 +58,12 @@ linters:
55
58
  bundle install
56
59
  bundle exec rake
57
60
  ```
61
+
62
+ ## Recommended extension
63
+
64
+ If you use VS Code, we highly encourage [ERB Linter extension](https://marketplace.visualstudio.com/items?itemName=manuelpuyol.erb-linter) to see immediate feedback in your editor.
65
+
66
+ ## Note
67
+
68
+ 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.
@@ -64,6 +64,13 @@ module ERBLint
64
64
  add_offense(processed_source.to_source_range(tag.loc), offense, replacement)
65
65
  end
66
66
 
67
+ def generate_offense_from_source_range(klass, source_range, message = nil, replacement = nil)
68
+ message ||= klass::MESSAGE
69
+ message += "\nLearn more at https://github.com/github/erblint-github#rules.\n"
70
+ offense = ["#{simple_class_name}:#{message}", source_range.source].join("\n")
71
+ add_offense(source_range, offense, replacement)
72
+ end
73
+
67
74
  def possible_attribute_values(tag, attr_name)
68
75
  value = tag.attributes[attr_name]&.value || nil
69
76
  basic_conditional_code_check(value || "") || [value].compact
@@ -0,0 +1,140 @@
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 AvoidGenericLinkTextCounter < Linter
10
+ include ERBLint::Linters::CustomHelpers
11
+ include LinterRegistry
12
+
13
+ BANNED_GENERIC_TEXT = [
14
+ "Read more",
15
+ "Learn more",
16
+ "Click here",
17
+ "More",
18
+ "Link",
19
+ "Here"
20
+ ].freeze
21
+ ARIA_LABEL_ATTRIBUTES = %w[aria-labelledby aria-label].freeze
22
+
23
+ MESSAGE = "Avoid using generic link text such as #{BANNED_GENERIC_TEXT.join(', ')} which do not make sense in isolation."
24
+
25
+ def run(processed_source)
26
+ processed_source.ast.children.each_with_index do |node, index|
27
+ next unless node.methods.include?(:type) && node.type == :text
28
+
29
+ text = node.children.join.strip
30
+
31
+ # Checks HTML tags
32
+ if banned_text?(text)
33
+ prev_node = processed_source.ast.children[index - 1]
34
+ next_node = processed_source.ast.children[index + 1]
35
+
36
+ next unless tag_type?(prev_node) && tag_type?(next_node)
37
+
38
+ text_node_tag = BetterHtml::Tree::Tag.from_node(node)
39
+ prev_node_tag = BetterHtml::Tree::Tag.from_node(prev_node)
40
+ next_node_tag = BetterHtml::Tree::Tag.from_node(next_node)
41
+
42
+ aria_label = possible_attribute_values(prev_node_tag, "aria-label")
43
+ aria_labelledby = possible_attribute_values(prev_node_tag, "aria-labelledby")
44
+
45
+ # Checks if nested between two link tags.
46
+ if link_tag?(prev_node_tag) && link_tag?(next_node_tag) && next_node_tag.closing?
47
+ # Skip because we cannot reliably check accessible name from aria-labelledby, or an aria-label that is set to a variable
48
+ # with static code analysis.
49
+ next if aria_labelledby.present? || (aria_label.present? && aria_label.join.include?("<%="))
50
+ # Skip because aria-label starts with visible text which we allow. Related to Success Criterion 2.5.3: Label in Name
51
+ next if aria_label.present? && valid_accessible_name?(aria_label.join, text)
52
+
53
+ range = prev_node_tag.loc.begin_pos...text_node_tag.loc.end_pos
54
+ source_range = processed_source.to_source_range(range)
55
+ generate_offense_from_source_range(self.class, source_range)
56
+ end
57
+ end
58
+
59
+ # Checks Rails link helpers like `link_to`
60
+ node.descendants(:erb).each do |erb_node|
61
+ _, _, code_node = *erb_node
62
+ source = code_node.loc.source
63
+ ruby_node = extract_ruby_node(source)
64
+ send_node = ruby_node&.descendants(:send)&.first
65
+ next unless send_node.methods.include?(:method_name) && send_node.method_name == :link_to
66
+
67
+ banned_text = nil
68
+
69
+ send_node.child_nodes.each do |child_node|
70
+ banned_text = child_node.children.join if child_node.methods.include?(:type) && child_node.type == :str && banned_text?(child_node.children.join)
71
+ next if banned_text.blank?
72
+ next unless child_node.methods.include?(:type) && child_node.type == :hash
73
+
74
+ child_node.descendants(:pair).each do |pair_node|
75
+ next unless pair_node.children.first.type?(:sym)
76
+
77
+ # Skips if `link_to` has `aria-labelledby` or `aria-label` which cannot be evaluated accurately with ERB lint alone.
78
+ # ERB lint removes Ruby string interpolation so the `aria-label` for "<%= link_to 'Learn more', "aria-label": "Learn #{@some_variable}" %>" will
79
+ # only be `Learn` which is unreliable so we can't do checks :(
80
+ key_value = pair_node.children.first.children.join
81
+ banned_text = nil if ARIA_LABEL_ATTRIBUTES.include?(key_value)
82
+ next unless key_value == "aria"
83
+
84
+ pair_node.children[1].descendants(:sym).each do |sym_node|
85
+ banned_text = nil if sym_node.children.join == "label" || sym_node.children.join == "labelledby"
86
+ end
87
+ end
88
+ end
89
+ if banned_text.present?
90
+ tag = BetterHtml::Tree::Tag.from_node(code_node)
91
+ generate_offense(self.class, processed_source, tag)
92
+ end
93
+ banned_text = nil
94
+ end
95
+ end
96
+ counter_correct?(processed_source)
97
+ end
98
+
99
+ def autocorrect(processed_source, offense)
100
+ return unless offense.context
101
+
102
+ lambda do |corrector|
103
+ if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
104
+ # update the counter if exists
105
+ corrector.replace(offense.source_range, offense.context)
106
+ else
107
+ # add comment with counter if none
108
+ corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
109
+ end
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def banned_text?(text)
116
+ BANNED_GENERIC_TEXT.map(&:downcase).include?(text.downcase.gsub(/\W+/, " ").strip)
117
+ end
118
+
119
+ def valid_accessible_name?(aria_label, text)
120
+ aria_label.downcase.include?(text.downcase)
121
+ end
122
+
123
+ def extract_ruby_node(source)
124
+ BetterHtml::TestHelper::RubyNode.parse(source)
125
+ rescue ::Parser::SyntaxError
126
+ nil
127
+ end
128
+
129
+ def link_tag?(tag_node)
130
+ tag_node.name == "a"
131
+ end
132
+
133
+ def tag_type?(node)
134
+ node.methods.include?(:type) && node.type == :tag
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -10,8 +10,8 @@ module ERBLint
10
10
  include ERBLint::Linters::CustomHelpers
11
11
  include LinterRegistry
12
12
 
13
- MESSAGE = "<iframe> with meaningful content should have a title attribute that identifies the content."\
14
- " If <iframe> has no meaningful content, hide it from assistive technology with `aria-hidden='true'`."\
13
+ MESSAGE = "`<iframe>` with meaningful content should have a title attribute that identifies the content."\
14
+ " If `<iframe>` has no meaningful content, hide it from assistive technology with `aria-hidden='true'`."\
15
15
 
16
16
  def run(processed_source)
17
17
  tags(processed_source).each do |tag|
data/lib/tasks/docs.rake CHANGED
@@ -4,10 +4,9 @@ namespace :docs do
4
4
  task :coverage do
5
5
  require "erb_lint/all"
6
6
  require "erblint-github/linters"
7
-
8
- Dir[File.join(__dir__, "linters", "github/**/*.rb")].sort.each do |file|
7
+ Dir[File.join("lib", "erblint-github", "linters", "github/**/*.rb")].sort.each do |file|
9
8
  rule_documentation_path = file
10
- .gsub("#{__dir__}linters/github/", "docs/rules/")
9
+ .gsub("lib/erblint-github/linters/github/", "docs/rules/")
11
10
  .gsub(".rb", ".md")
12
11
  .tr("_", "-")
13
12
  raise "Missing rule documentation. Please document rule in #{rule_documentation_path}" unless File.file?(rule_documentation_path.to_s)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :tests do
4
+ task :coverage do
5
+ require "erb_lint/all"
6
+ require "erblint-github/linters"
7
+
8
+ Dir[File.join("lib", "erblint-github", "linters", "github/**/*.rb")].sort.each do |file|
9
+ test_path = file.gsub("lib/erblint-github/linters/github/", "test/linters/").gsub(".rb", "_test.rb")
10
+ raise "Missing test. Please add test in #{test_path}" unless File.file?(test_path.to_s)
11
+ end
12
+ puts "All rules have test coverage."
13
+ end
14
+ 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.5
4
+ version: 0.0.8
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-02-23 00:00:00.000000000 Z
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: erb_lint
@@ -16,84 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.1'
19
+ version: 0.1.1
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.1'
26
+ version: 0.1.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '5.14'
33
+ version: '5.15'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '5.14'
40
+ version: '5.15'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mocha
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1'
47
+ version: '1.14'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1'
54
+ version: '1.14'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '12.0'
61
+ version: 13.0.6
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '12.0'
68
+ version: 13.0.6
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rubocop
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: 1.13.0
75
+ version: 1.30.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 1.13.0
82
+ version: 1.30.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rubocop-github
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.16.0
89
+ version: 0.17.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.16.0
96
+ version: 0.17.0
97
97
  description: Template style checking for GitHub Ruby repositories
98
98
  email:
99
99
  - opensource+erblint-github@github.com
@@ -106,6 +106,7 @@ files:
106
106
  - lib/erblint-github/linters.rb
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
+ - lib/erblint-github/linters/github/accessibility/avoid_generic_link_text_counter.rb
109
110
  - lib/erblint-github/linters/github/accessibility/iframe_has_title.rb
110
111
  - lib/erblint-github/linters/github/accessibility/image_has_alt.rb
111
112
  - lib/erblint-github/linters/github/accessibility/no_aria_label_misuse_counter.rb
@@ -113,10 +114,12 @@ files:
113
114
  - lib/erblint-github/linters/github/accessibility/no_redundant_image_alt.rb
114
115
  - lib/erblint-github/linters/github/accessibility/no_title_attribute_counter.rb
115
116
  - lib/tasks/docs.rake
117
+ - lib/tasks/tests.rake
116
118
  homepage: https://github.com/github/erblint-github
117
119
  licenses:
118
120
  - MIT
119
- metadata: {}
121
+ metadata:
122
+ rubygems_mfa_required: 'true'
120
123
  post_install_message:
121
124
  rdoc_options: []
122
125
  require_paths:
@@ -125,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
128
  requirements:
126
129
  - - ">="
127
130
  - !ruby/object:Gem::Version
128
- version: 2.5.0
131
+ version: 2.6.0
129
132
  required_rubygems_version: !ruby/object:Gem::Requirement
130
133
  requirements:
131
134
  - - ">="