a11y-lint 0.4.0 → 0.5.0
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/a11y/lint/rules/link_missing_accessible_name.rb +105 -0
- data/lib/a11y/lint/version.rb +1 -1
- data/lib/a11y/lint.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7cb772a75e7dc2bc7f36f7869efac31d0f9281a196ef237f57e2e2db4f3116c1
|
|
4
|
+
data.tar.gz: ddb88dd42e274631a6d289ab56b2c0353e305b37816b9232bd7689578d19ed87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 98b46acd6a78f33f546b6c2aa88e6ff40f7ffe89aaf196d3df0b355a7e2cd2a96d0fdc74c89796764953d47e40b3e89a26dc0008ae55528c3585f48a4d31493d
|
|
7
|
+
data.tar.gz: 2571c7dbaef8b3c37cea2a6426b29586c1ad5d3e5d120504ff20c3d4b70cabe8578cbcd50d2f8c677056aabee4520f264eae642be07e610290a4303317031c41
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-03-31
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `LinkMissingAccessibleName` rule: detects `<a>` tags without accessible text content
|
|
15
|
+
|
|
10
16
|
## [0.4.0] - 2026-03-27
|
|
11
17
|
|
|
12
18
|
### Added
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ripper"
|
|
4
|
+
|
|
5
|
+
module A11y
|
|
6
|
+
module Lint
|
|
7
|
+
module Rules
|
|
8
|
+
# Checks that link_to / external_link_to calls with empty text
|
|
9
|
+
# include an aria-label (WCAG 4.1.2).
|
|
10
|
+
class LinkMissingAccessibleName < Rule
|
|
11
|
+
LINK_METHODS = %w[link_to external_link_to].freeze
|
|
12
|
+
|
|
13
|
+
def check(node)
|
|
14
|
+
return unless link_with_empty_text_and_no_accessible_name?(node)
|
|
15
|
+
|
|
16
|
+
"link with empty text content requires an aria-label (WCAG 4.1.2)"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def link_with_empty_text_and_no_accessible_name?(node)
|
|
22
|
+
code = node.ruby_code
|
|
23
|
+
return false unless code
|
|
24
|
+
|
|
25
|
+
sexp = Ripper.sexp(code)
|
|
26
|
+
return false unless sexp
|
|
27
|
+
|
|
28
|
+
call = extract_link_call(sexp)
|
|
29
|
+
return false unless call
|
|
30
|
+
|
|
31
|
+
first_arg_empty_string?(call) && !aria_label_within?(call)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def extract_link_call(sexp)
|
|
35
|
+
return unless sexp.is_a?(Array)
|
|
36
|
+
return sexp if link_call?(sexp)
|
|
37
|
+
|
|
38
|
+
sexp.each do |child|
|
|
39
|
+
result = extract_link_call(child)
|
|
40
|
+
return result if result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def link_call?(sexp)
|
|
47
|
+
case sexp
|
|
48
|
+
in [:command, [:@ident, name, *], *] if LINK_METHODS.include?(name) then true
|
|
49
|
+
in [:method_add_arg, [:fcall, [:@ident, name, *]], *] if LINK_METHODS.include?(name) then true
|
|
50
|
+
else false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def first_arg_empty_string?(call)
|
|
55
|
+
args = extract_args(call)
|
|
56
|
+
return false unless args&.first
|
|
57
|
+
|
|
58
|
+
args.first in [:string_literal, [:string_content]]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def extract_args(call)
|
|
62
|
+
case call
|
|
63
|
+
in [:command, _, [:args_add_block, args, *]] then args
|
|
64
|
+
in [:method_add_arg, _, [:arg_paren, [:args_add_block, args, *]]] then args
|
|
65
|
+
else nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def aria_label_within?(sexp)
|
|
70
|
+
return true if aria_hash_with_label?(sexp)
|
|
71
|
+
return true if aria_label_string_key?(sexp)
|
|
72
|
+
return false unless sexp.is_a?(Array)
|
|
73
|
+
|
|
74
|
+
sexp.any? { |child| aria_label_within?(child) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def aria_hash_with_label?(sexp)
|
|
78
|
+
return false unless sexp.is_a?(Array) && sexp[0] == :assoc_new
|
|
79
|
+
|
|
80
|
+
key = sexp[1]
|
|
81
|
+
value = sexp[2]
|
|
82
|
+
|
|
83
|
+
(key in [:@label, "aria:", *]) && label_key_within?(value)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def label_key_within?(sexp)
|
|
87
|
+
return true if label_key?(sexp)
|
|
88
|
+
return false unless sexp.is_a?(Array)
|
|
89
|
+
|
|
90
|
+
sexp.any? { |child| label_key_within?(child) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def label_key?(sexp)
|
|
94
|
+
sexp.is_a?(Array) && sexp[0] == :assoc_new && (sexp[1] in [:@label, "label:", *])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def aria_label_string_key?(sexp)
|
|
98
|
+
return false unless sexp.is_a?(Array) && sexp[0] == :assoc_new
|
|
99
|
+
|
|
100
|
+
sexp[1] in [:string_literal, [:string_content, [:@tstring_content, "aria-label", *]]]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/a11y/lint/version.rb
CHANGED
data/lib/a11y/lint.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "lint/erb_node"
|
|
|
9
9
|
require_relative "lint/rule"
|
|
10
10
|
require_relative "lint/rules/image_tag_missing_alt"
|
|
11
11
|
require_relative "lint/rules/img_missing_alt"
|
|
12
|
+
require_relative "lint/rules/link_missing_accessible_name"
|
|
12
13
|
require_relative "lint/slim_runner"
|
|
13
14
|
require_relative "lint/erb_runner"
|
|
14
15
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: a11y-lint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abdullah Hashim
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- lib/a11y/lint/rule.rb
|
|
64
64
|
- lib/a11y/lint/rules/image_tag_missing_alt.rb
|
|
65
65
|
- lib/a11y/lint/rules/img_missing_alt.rb
|
|
66
|
+
- lib/a11y/lint/rules/link_missing_accessible_name.rb
|
|
66
67
|
- lib/a11y/lint/slim_runner.rb
|
|
67
68
|
- lib/a11y/lint/version.rb
|
|
68
69
|
- sig/a11y/lint.rbs
|