erblint-github 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46d00ab6d8c5bc82e5ea9e42c316037af0509e6f16a107cfd7762c21bac5c762
4
- data.tar.gz: 87f2785cf56cd632f122eb9ddb2e369298902737e281ca75de514fffa45de514
3
+ metadata.gz: 310138254b0989437e8cf803d52cba66ad005d7cae43b8b6233e638a20e40868
4
+ data.tar.gz: 654d731eb0c99317883327dd401726582d7df70995350d0006b365fe1b9670b6
5
5
  SHA512:
6
- metadata.gz: efd49409b7c581f29c45bcc884279bfcff48baefa63ec7e53e028980589b33a87fa655e20a39bb919167cb078e27a6e07ab3c7f4323eec7b02f6d258e1a7f489
7
- data.tar.gz: 24a5f6882e6795950545a47fb87c6e8950fc588976ffaa2c7135f9b363a642743f45ebc2e37ff9748360292249b5e36003c89dc8ec3adc6c77499ea12bc9336e
6
+ metadata.gz: ce1a90a1c2482b29b406787a1c530614718242de1b9fac828617c2f476db00578872bcddf52037bf8195c71beb8750916e853b5127f4de036aea5771a8edbe2b
7
+ data.tar.gz: 2a1e8844fdae5d352a6798937629e4ae70bea7d3a16c00e1a6de363dcc805044164b55e5166d9ffaf6f288bbdb135c373c2f1b1b40d27ecb0705c2cb616a833c
data/README.md CHANGED
@@ -25,20 +25,30 @@ require "erblint-github/linters"
25
25
  linters:
26
26
  GitHub::Accessibility::AvoidBothDisabledAndAriaDisabled:
27
27
  enabled: true
28
+ GitHub::Accessibility::IframeHasTitle:
29
+ enabled: true
28
30
  GitHub::Accessibility::ImageHasAlt:
29
31
  enabled: true
30
- GitHub::Accessibility::NoAriaLabelMisuse:
32
+ GitHub::Accessibility::NoAriaLabelMisuseCounter:
33
+ enabled: true
34
+ GitHub::Accessibility::NoPositiveTabIndex:
31
35
  enabled: true
32
36
  GitHub::Accessibility::NoRedundantImageAlt:
33
37
  enabled: true
38
+ GitHub::Accessibility::NoTitleAttributeCounter:
39
+ enabled: true
34
40
  ```
35
41
 
36
42
  ## Rules
37
43
 
38
44
  - [GitHub::Accessibility::AvoidBothDisabledAndAriaDisabled](./docs/rules/accessibility/avoid-both-disabled-and-aria-disabled.md)
45
+ - [GitHub::Accessibility::AvoidGenericLinkTextCounter](./docs/rules/accessibility/avoid-generic-link-text-counter.md)
46
+ - [GitHub::Accessibility::IframeHasTitle](./docs/rules/accessibility/iframe-has-title.md)
39
47
  - [GitHub::Accessibility::ImageHasAlt](./docs/rules/accessibility/image-has-alt.md)
40
- - [GitHub::Accessibility::NoAriaLabelMisuse](./docs/rules/accessibility/no-aria-label-misuse.md)
48
+ - [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md)
49
+ - [GitHub::Accessibility::NoPositiveTabIndex](./docs/rules/accessibility/no-positive-tab-index.md)
41
50
  - [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md)
51
+ - [GitHub::Accessibility::NoTitleAttributeCounter](./docs/rules/accessibility/no-title-attribute-counter.md)
42
52
 
43
53
  ## Testing
44
54
 
@@ -24,6 +24,39 @@ module ERBLint
24
24
  end
25
25
  end
26
26
 
27
+ def counter_correct?(processed_source)
28
+ comment_node = nil
29
+ expected_count = 0
30
+ rule_name = simple_class_name
31
+ offenses_count = @offenses.length
32
+
33
+ processed_source.parser.ast.descendants(:erb).each do |node|
34
+ indicator_node, _, code_node, = *node
35
+ indicator = indicator_node&.loc&.source
36
+ comment = code_node&.loc&.source&.strip
37
+
38
+ if indicator == "#" && comment.start_with?("erblint:counter") && comment.match(rule_name)
39
+ comment_node = node
40
+ expected_count = comment.match(/\s(\d+)\s?$/)[1].to_i
41
+ end
42
+ end
43
+
44
+ if offenses_count.zero?
45
+ # have to adjust to get `\n` so we delete the whole line
46
+ add_offense(processed_source.to_source_range(comment_node.loc.adjust(end_pos: 1)), "Unused erblint:counter comment for #{rule_name}", "") if comment_node
47
+ return
48
+ end
49
+
50
+ first_offense = @offenses[0]
51
+
52
+ if comment_node.nil?
53
+ add_offense(processed_source.to_source_range(first_offense.source_range), "#{rule_name}: If you must, add <%# erblint:counter #{rule_name} #{offenses_count} %> to bypass this check.", "<%# erblint:counter #{rule_name} #{offenses_count} %>")
54
+ else
55
+ clear_offenses
56
+ add_offense(processed_source.to_source_range(comment_node.loc), "Incorrect erblint:counter number for #{rule_name}. Expected: #{expected_count}, actual: #{offenses_count}.", "<%# erblint:counter #{rule_name} #{offenses_count} %>") if expected_count != offenses_count
57
+ end
58
+ end
59
+
27
60
  def generate_offense(klass, processed_source, tag, message = nil, replacement = nil)
28
61
  message ||= klass::MESSAGE
29
62
  message += "\nLearn more at https://github.com/github/erblint-github#rules.\n"
@@ -31,6 +64,13 @@ module ERBLint
31
64
  add_offense(processed_source.to_source_range(tag.loc), offense, replacement)
32
65
  end
33
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
+
34
74
  def possible_attribute_values(tag, attr_name)
35
75
  value = tag.attributes[attr_name]&.value || nil
36
76
  basic_conditional_code_check(value || "") || [value].compact
@@ -0,0 +1,104 @@
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
+ MESSAGE = "Avoid using generic link text such as #{BANNED_GENERIC_TEXT.join(', ')} which do not make sense in isolation."
22
+
23
+ def run(processed_source)
24
+ processed_source.ast.children.each_with_index do |node, index|
25
+ next unless node.methods.include?(:type) && node.type == :text
26
+
27
+ text = node.children.join.strip
28
+ # Checks HTML tags
29
+ if banned_text?(text)
30
+ prev_node = processed_source.ast.children[index - 1]
31
+ next_node = processed_source.ast.children[index + 1]
32
+
33
+ next unless tag_type?(prev_node) && tag_type?(next_node)
34
+
35
+ text_node_tag = BetterHtml::Tree::Tag.from_node(node)
36
+ prev_node_tag = BetterHtml::Tree::Tag.from_node(prev_node)
37
+ next_node_tag = BetterHtml::Tree::Tag.from_node(next_node)
38
+
39
+ # We only report if the text is nested between two link tags.
40
+ if link_tag?(prev_node_tag) && link_tag?(next_node_tag) && next_node_tag.closing?
41
+ range = prev_node_tag.loc.begin_pos...text_node_tag.loc.end_pos
42
+ source_range = processed_source.to_source_range(range)
43
+ generate_offense_from_source_range(self.class, source_range)
44
+ end
45
+ end
46
+
47
+ # Checks Rails link helpers like `link_to`
48
+ erb_node = node.type == :erb ? node : node.descendants(:erb).first
49
+ next unless erb_node
50
+
51
+ _, _, code_node = *erb_node
52
+ source = code_node.loc.source
53
+ ruby_node = extract_ruby_node(source)
54
+ send_node = ruby_node&.descendants(:send)&.first
55
+ next unless send_node.methods.include?(:method_name) && send_node.method_name == :link_to
56
+
57
+ send_node.child_nodes.each do |child_node|
58
+ if child_node.methods.include?(:type) && child_node.type == :str && banned_text?(child_node.children.join)
59
+ tag = BetterHtml::Tree::Tag.from_node(code_node)
60
+ generate_offense(self.class, processed_source, tag)
61
+ end
62
+ end
63
+ end
64
+ counter_correct?(processed_source)
65
+ end
66
+
67
+ def autocorrect(processed_source, offense)
68
+ return unless offense.context
69
+
70
+ lambda do |corrector|
71
+ if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
72
+ # update the counter if exists
73
+ corrector.replace(offense.source_range, offense.context)
74
+ else
75
+ # add comment with counter if none
76
+ corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
77
+ end
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def banned_text?(text)
84
+ BANNED_GENERIC_TEXT.map(&:downcase).include?(text.downcase)
85
+ end
86
+
87
+ def extract_ruby_node(source)
88
+ BetterHtml::TestHelper::RubyNode.parse(source)
89
+ rescue ::Parser::SyntaxError
90
+ nil
91
+ end
92
+
93
+ def link_tag?(tag_node)
94
+ tag_node.name == "a"
95
+ end
96
+
97
+ def tag_type?(node)
98
+ node.methods.include?(:type) && node.type == :tag
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,38 @@
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 IframeHasTitle < Linter
10
+ include ERBLint::Linters::CustomHelpers
11
+ include LinterRegistry
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'`."\
15
+
16
+ def run(processed_source)
17
+ tags(processed_source).each do |tag|
18
+ next if tag.name != "iframe"
19
+ next if tag.closing?
20
+
21
+ title = possible_attribute_values(tag, "title")
22
+
23
+ generate_offense(self.class, processed_source, tag) if title.empty? && !aria_hidden?(tag)
24
+ end
25
+
26
+ rule_disabled?(processed_source)
27
+ end
28
+
29
+ private
30
+
31
+ def aria_hidden?(tag)
32
+ tag.attributes["aria-hidden"]&.value&.present?
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -6,7 +6,7 @@ module ERBLint
6
6
  module Linters
7
7
  module GitHub
8
8
  module Accessibility
9
- class NoAriaLabelMisuse < Linter
9
+ class NoAriaLabelMisuseCounter < Linter
10
10
  include ERBLint::Linters::CustomHelpers
11
11
  include LinterRegistry
12
12
 
@@ -34,7 +34,21 @@ module ERBLint
34
34
  end
35
35
  end
36
36
  end
37
- rule_disabled?(processed_source)
37
+ counter_correct?(processed_source)
38
+ end
39
+
40
+ def autocorrect(processed_source, offense)
41
+ return unless offense.context
42
+
43
+ lambda do |corrector|
44
+ if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
45
+ # update the counter if exists
46
+ corrector.replace(offense.source_range, offense.context)
47
+ else
48
+ # add comment with counter if none
49
+ corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
50
+ end
51
+ end
38
52
  end
39
53
  end
40
54
  end
@@ -0,0 +1,29 @@
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 NoPositiveTabIndex < Linter
10
+ include ERBLint::Linters::CustomHelpers
11
+ include LinterRegistry
12
+
13
+ MESSAGE = "Do not use positive tabindex as it is error prone and can severely disrupt navigation experience for keyboard users"
14
+
15
+ def run(processed_source)
16
+ tags(processed_source).each do |tag|
17
+ next if tag.closing?
18
+ next unless tag.attributes["tabindex"]&.value.to_i.positive?
19
+
20
+ generate_offense(self.class, processed_source, tag)
21
+ end
22
+
23
+ rule_disabled?(processed_source)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
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 NoTitleAttributeCounter < Linter
10
+ include ERBLint::Linters::CustomHelpers
11
+ include LinterRegistry
12
+
13
+ MESSAGE = "The title attribute should never be used unless for an `<iframe>` as it is inaccessible for several groups of users."
14
+
15
+ def run(processed_source)
16
+ tags(processed_source).each do |tag|
17
+ next if tag.name == "iframe"
18
+ next if tag.closing?
19
+
20
+ title = possible_attribute_values(tag, "title")
21
+ generate_offense(self.class, processed_source, tag) if title.present?
22
+ end
23
+
24
+ counter_correct?(processed_source)
25
+ end
26
+
27
+ def autocorrect(processed_source, offense)
28
+ return unless offense.context
29
+
30
+ lambda do |corrector|
31
+ if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
32
+ # update the counter if exists
33
+ corrector.replace(offense.source_range, offense.context)
34
+ else
35
+ # add comment with counter if none
36
+ corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
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.3
4
+ version: 0.0.6
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: 2021-12-29 00:00:00.000000000 Z
11
+ date: 2022-06-15 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,14 +106,20 @@ 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
110
+ - lib/erblint-github/linters/github/accessibility/iframe_has_title.rb
109
111
  - lib/erblint-github/linters/github/accessibility/image_has_alt.rb
110
- - lib/erblint-github/linters/github/accessibility/no_aria_label_misuse.rb
112
+ - lib/erblint-github/linters/github/accessibility/no_aria_label_misuse_counter.rb
113
+ - lib/erblint-github/linters/github/accessibility/no_positive_tab_index.rb
111
114
  - lib/erblint-github/linters/github/accessibility/no_redundant_image_alt.rb
115
+ - lib/erblint-github/linters/github/accessibility/no_title_attribute_counter.rb
112
116
  - lib/tasks/docs.rake
117
+ - lib/tasks/tests.rake
113
118
  homepage: https://github.com/github/erblint-github
114
119
  licenses:
115
120
  - MIT
116
- metadata: {}
121
+ metadata:
122
+ rubygems_mfa_required: 'true'
117
123
  post_install_message:
118
124
  rdoc_options: []
119
125
  require_paths:
@@ -122,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
128
  requirements:
123
129
  - - ">="
124
130
  - !ruby/object:Gem::Version
125
- version: 2.5.0
131
+ version: 2.6.0
126
132
  required_rubygems_version: !ruby/object:Gem::Requirement
127
133
  requirements:
128
134
  - - ">="