Skip to content

Commit ea9d80d

Browse files
committed
add counter support and title counter
1 parent 5f6d685 commit ea9d80d

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ linters:
3535
enabled: true
3636
GitHub::Accessibility::NoRedundantImageAlt:
3737
enabled: true
38+
GitHub::Accessibility::NoTitleAttributeCounter:
39+
enabled: true
3840
```
3941
4042
## Rules
@@ -45,6 +47,7 @@ linters:
4547
- [GitHub::Accessibility::NoAriaLabelMisuse](./docs/rules/accessibility/no-aria-label-misuse.md)
4648
- [GitHub::Accessibility::NoPositiveTabIndex](./docs/rules/accessibility/no-positive-tab-index.md)
4749
- [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md)
50+
- [GitHub::Accessibility::NoTitleAttributeCounter](./docs/rules/accessibility/no-title-attribute-counter.md)
4851
4952
## Testing
5053
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# No title attribute counter
2+
3+
## Rule Details
4+
5+
The `title` attribute is strongly discouraged. The only exception is on an `<iframe>` element. It is hardly useful and cannot be accessed by multiple groups of users including keyboard-only users and mobile users.
6+
7+
The `title` attribute is commonly seen set on links, matching the link text. This is redundant and unnecessary so it can be simply be removed.
8+
9+
If you are considering `title` attribute to provide supplementary description, consider whether the text in question can be persisted in the design. Alternatively, if it's important to display supplementary text that is hidden by default, consider using an **accessible** tooltip implementation that uses the `aria-labelledby` or `aria-describedby` semantics. Even so, proceed with caution: tooltips should only be used on interactive elements like links or buttons.
10+
11+
### Should I use `title` attribute to provide accessible name for `<svg>`?
12+
13+
Use a `<title>` element instead of the `title` attribute, or an `aria-label`.
14+
15+
### Resources
16+
17+
- [TPGI: Using the HTML title attribute ](https://www.tpgi.com/using-the-html-title-attribute/)
18+
- [The Trials and Tribulations of the Title Attribute](https://www.24a11y.com/2017/the-trials-and-tribulations-of-the-title-attribute/)
19+
20+
### 👎 Examples of **incorrect** code for this rule:
21+
22+
```erb
23+
<a title="A home for all developers" href="github.com">GitHub</a>
24+
```
25+
26+
```
27+
<a href="/" title="github.com">GitHub</a>
28+
```
29+
30+
### 👍 Examples of **correct** code for this rule:
31+
32+
```erb
33+
<a href="github.com" aria-describedby="description">GitHub</a>
34+
<p id="description" class="tooltip js-tooltip">A home for all developers</p>
35+
```
36+
37+
```
38+
<a href="github.com">GitHub</a>
39+
```

lib/erblint-github/linters/custom_helpers.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,41 @@ def rule_disabled?(processed_source)
2424
end
2525
end
2626

27+
def counter_correct?(processed_source)
28+
comment_node = nil
29+
expected_count = 0
30+
rule_name = self.class.name.match(/:?:?(\w+)\Z/)[1]
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:count") && 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 == 0
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:count 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+
if expected_count != offenses_count
57+
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} %>")
58+
end
59+
end
60+
end
61+
2762
def generate_offense(klass, processed_source, tag, message = nil, replacement = nil)
2863
message ||= klass::MESSAGE
2964
message += "\nLearn more at https://github.com/github/erblint-github#rules.\n"
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 is inaccesible to several groups of users. Please avoid setting unless for an `<iframe>`."
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+
22+
generate_offense(self.class, processed_source, tag) if title.present?
23+
end
24+
25+
counter_correct?(processed_source)
26+
end
27+
28+
private
29+
30+
def correct_counter(corrector, processed_source, offense)
31+
if processed_source.file_content.include?("erblint:counter DeprecatedInPrimerCounter")
32+
corrector.replace(offense.source_range, offense.context)
33+
else
34+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
35+
end
36+
end
37+
38+
def autocorrect(processed_source, offense)
39+
return unless offense.context
40+
41+
lambda do |corrector|
42+
if offense.context.include?("erblint:counter DeprecatedInPrimerCounter")
43+
correct_counter(corrector, processed_source, offense)
44+
end
45+
end
46+
end
47+
end
48+
end
49+
end
50+
end
51+
end

0 commit comments

Comments
 (0)