a11y-lint 0.11.0 → 0.12.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/cli.rb +6 -16
- data/lib/a11y/lint/configuration.rb +14 -0
- data/lib/a11y/lint/erb_element_node.rb +51 -6
- data/lib/a11y/lint/erb_runner.rb +51 -7
- data/lib/a11y/lint/phlex_node.rb +24 -5
- data/lib/a11y/lint/phlex_runner.rb +31 -7
- data/lib/a11y/lint/slim_node.rb +57 -3
- data/lib/a11y/lint/slim_runner.rb +5 -4
- data/lib/a11y/lint/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f62cdc346f6aa1f5c2cae674c365678a333479966aabd1b144e7901f90e7cdf
|
|
4
|
+
data.tar.gz: 874c7cb5bac0680cb58fb2f73e89304c21862686010f5b0e721aad19861c0bbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5757d27c00656b68b5b4209a153da09e720d190bacb5124e1860a03a53d3e0df4d02909a2c1d667c5be477394228274a6e64c95b4a032d64cb14a65ba72f5bd7
|
|
7
|
+
data.tar.gz: bf502097c82848041313039b29271808133c1ff3be50b1d8e4b31b5d7df7bb1c35bb1ae643b869143355bbcd3dd2cfcddf98acd44ac858f4edb436e5699b3b92
|
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.12.0] - 2026-04-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `.a11y-lint.yml` now supports a top-level `hidden_wrapper_classes` list; content inside elements whose class matches is treated as hidden from assistive technology when the four accessible-name rules (`AnchorMissingAccessibleName`, `ButtonMissingAccessibleName`, `LinkToMissingAccessibleName`, `ButtonTagMissingAccessibleName`) determine whether a button/link has an accessible name. Opt-in; default is no filtering
|
|
15
|
+
|
|
10
16
|
## [0.11.0] - 2026-04-21
|
|
11
17
|
|
|
12
18
|
### Added
|
data/lib/a11y/lint/cli.rb
CHANGED
|
@@ -71,10 +71,10 @@ module A11y
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def lint_files(files)
|
|
74
|
-
|
|
75
|
-
slim_runner = SlimRunner.new(
|
|
76
|
-
erb_runner = ErbRunner.new(
|
|
77
|
-
phlex_runner = PhlexRunner.new(
|
|
74
|
+
configuration = load_configuration
|
|
75
|
+
slim_runner = SlimRunner.new(configuration:)
|
|
76
|
+
erb_runner = ErbRunner.new(configuration:)
|
|
77
|
+
phlex_runner = PhlexRunner.new(configuration:)
|
|
78
78
|
|
|
79
79
|
files.flat_map do |file|
|
|
80
80
|
source = File.read(file)
|
|
@@ -91,18 +91,8 @@ module A11y
|
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
@config_path,
|
|
97
|
-
search_path: @argv.first || "."
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
Rules.constants.filter_map do |name|
|
|
101
|
-
klass = Rules.const_get(name)
|
|
102
|
-
next unless klass.is_a?(Class) && klass < NodeRule
|
|
103
|
-
|
|
104
|
-
klass if configuration.enabled?(klass.rule_name)
|
|
105
|
-
end
|
|
94
|
+
def load_configuration
|
|
95
|
+
Configuration.load(@config_path, search_path: @argv.first || ".")
|
|
106
96
|
end
|
|
107
97
|
|
|
108
98
|
def print_results(offenses)
|
|
@@ -41,6 +41,20 @@ module A11y
|
|
|
41
41
|
|
|
42
42
|
@config.dig(rule_name, "Enabled") != false
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
def hidden_wrapper_classes
|
|
46
|
+
@hidden_wrapper_classes ||=
|
|
47
|
+
Array(@config["hidden_wrapper_classes"]).map(&:to_s).freeze
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def enabled_rules
|
|
51
|
+
Rules.constants.filter_map do |name|
|
|
52
|
+
klass = Rules.const_get(name)
|
|
53
|
+
next unless klass.is_a?(Class) && klass < NodeRule
|
|
54
|
+
|
|
55
|
+
klass if enabled?(klass.rule_name)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
44
58
|
end
|
|
45
59
|
end
|
|
46
60
|
end
|
|
@@ -5,12 +5,14 @@ module A11y
|
|
|
5
5
|
# Wraps a Nokogiri HTML element from an ERB template
|
|
6
6
|
# as a queryable node for lint rules.
|
|
7
7
|
class ErbElementNode
|
|
8
|
-
attr_reader :line
|
|
8
|
+
attr_reader :line, :configuration
|
|
9
9
|
|
|
10
|
-
def initialize(
|
|
10
|
+
def initialize(
|
|
11
|
+
nokogiri_node:, line:, configuration: Configuration.new
|
|
12
|
+
)
|
|
11
13
|
@nokogiri_node = nokogiri_node
|
|
12
14
|
@line = line
|
|
13
|
-
@
|
|
15
|
+
@configuration = configuration
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def tag_name
|
|
@@ -45,15 +47,58 @@ module A11y
|
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def text_content?
|
|
48
|
-
|
|
50
|
+
return false if hidden_wrapper?(@nokogiri_node)
|
|
51
|
+
|
|
52
|
+
visible_text_or_output?(@nokogiri_node)
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
# Returns direct element children wrapped as ErbElementNode objects.
|
|
56
|
+
# Excludes elements whose class attribute matches a configured
|
|
57
|
+
# hidden-wrapper class, since CSS-hidden subtrees do not contribute
|
|
58
|
+
# to the accessible name.
|
|
52
59
|
def children
|
|
53
|
-
@nokogiri_node.element_children.
|
|
54
|
-
|
|
60
|
+
@nokogiri_node.element_children.filter_map do |child|
|
|
61
|
+
next if hidden_wrapper?(child)
|
|
62
|
+
|
|
63
|
+
ErbElementNode.new(
|
|
64
|
+
nokogiri_node: child, line: child.line,
|
|
65
|
+
configuration: configuration
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def visible_text_or_output?(node)
|
|
73
|
+
return false if hidden_wrapper?(node)
|
|
74
|
+
return true if own_text_or_marker?(node)
|
|
75
|
+
|
|
76
|
+
node.element_children.any? { |c| visible_text_or_output?(c) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def own_text_or_marker?(node)
|
|
80
|
+
node.children.any? do |c|
|
|
81
|
+
next false unless c.text?
|
|
82
|
+
|
|
83
|
+
content = c.content
|
|
84
|
+
content.include?(ErbRunner::ERB_OUTPUT_MARKER) ||
|
|
85
|
+
!content.strip.empty?
|
|
55
86
|
end
|
|
56
87
|
end
|
|
88
|
+
|
|
89
|
+
def hidden_wrapper?(node)
|
|
90
|
+
classes = configuration.hidden_wrapper_classes
|
|
91
|
+
return false if classes.empty?
|
|
92
|
+
|
|
93
|
+
node_classes(node).any? { |klass| classes.include?(klass) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def node_classes(node)
|
|
97
|
+
return [] unless node.respond_to?(:attributes)
|
|
98
|
+
|
|
99
|
+
value = node.attributes["class"]&.value
|
|
100
|
+
value.is_a?(String) ? value.split : []
|
|
101
|
+
end
|
|
57
102
|
end
|
|
58
103
|
end
|
|
59
104
|
end
|
data/lib/a11y/lint/erb_runner.rb
CHANGED
|
@@ -14,8 +14,9 @@ module A11y
|
|
|
14
14
|
link meta param source track wbr
|
|
15
15
|
].freeze
|
|
16
16
|
|
|
17
|
-
def initialize(rules)
|
|
18
|
-
@rules = rules
|
|
17
|
+
def initialize(rules = nil, configuration: Configuration.new)
|
|
18
|
+
@rules = rules || configuration.enabled_rules
|
|
19
|
+
@configuration = configuration
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def run(source, filename:)
|
|
@@ -30,7 +31,7 @@ module A11y
|
|
|
30
31
|
|
|
31
32
|
private
|
|
32
33
|
|
|
33
|
-
attr_reader :rules
|
|
34
|
+
attr_reader :rules, :configuration
|
|
34
35
|
|
|
35
36
|
def check_html_nodes(source)
|
|
36
37
|
html = source.gsub(ERB_OUTPUT_TAG, ERB_OUTPUT_MARKER)
|
|
@@ -50,7 +51,7 @@ module A11y
|
|
|
50
51
|
ErbElementNode.new(
|
|
51
52
|
nokogiri_node: nokogiri_node,
|
|
52
53
|
line: nokogiri_node.line,
|
|
53
|
-
|
|
54
|
+
configuration: configuration
|
|
54
55
|
)
|
|
55
56
|
end
|
|
56
57
|
|
|
@@ -88,10 +89,53 @@ module A11y
|
|
|
88
89
|
return [nil, false] unless end_match
|
|
89
90
|
|
|
90
91
|
block_content = rest[0...end_match.begin(0)]
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
visible_codes_and_text(block_content)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns [visible_codes, visible_non_output_text?] where "visible"
|
|
96
|
+
# means not inside a hidden-wrapper element (per configuration).
|
|
97
|
+
def visible_codes_and_text(block_content)
|
|
98
|
+
indexed_codes = []
|
|
99
|
+
html = indexed_marker_html(block_content, indexed_codes)
|
|
100
|
+
fragment = Nokogiri::HTML4::DocumentFragment.parse(html)
|
|
101
|
+
strip_hidden_wrappers!(fragment)
|
|
102
|
+
|
|
103
|
+
remaining = fragment.to_html
|
|
104
|
+
visible_codes = indexed_codes.each_with_index.filter_map do |code, i|
|
|
105
|
+
code if remaining.include?("#{ERB_OUTPUT_MARKER}#{i}_")
|
|
106
|
+
end
|
|
107
|
+
[visible_codes, non_marker_text?(remaining)]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def indexed_marker_html(block_content, codes)
|
|
111
|
+
block_content.gsub(ERB_OUTPUT_TAG) do
|
|
112
|
+
codes << Regexp.last_match(1).strip
|
|
113
|
+
"#{ERB_OUTPUT_MARKER}#{codes.length - 1}_"
|
|
114
|
+
end.gsub(ERB_TAG, "")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def non_marker_text?(html)
|
|
118
|
+
!html.gsub(/#{ERB_OUTPUT_MARKER}\d+_/, "").strip.empty?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def strip_hidden_wrappers!(node)
|
|
122
|
+
return if configuration.hidden_wrapper_classes.empty?
|
|
123
|
+
|
|
124
|
+
node.element_children.each do |child|
|
|
125
|
+
if hidden_wrapper_element?(child)
|
|
126
|
+
child.remove
|
|
127
|
+
else
|
|
128
|
+
strip_hidden_wrappers!(child)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def hidden_wrapper_element?(node)
|
|
134
|
+
value = node.attributes["class"]&.value
|
|
135
|
+
return false unless value.is_a?(String)
|
|
93
136
|
|
|
94
|
-
|
|
137
|
+
classes = configuration.hidden_wrapper_classes
|
|
138
|
+
value.split.any? { |klass| classes.include?(klass) }
|
|
95
139
|
end
|
|
96
140
|
|
|
97
141
|
def check_node(node)
|
data/lib/a11y/lint/phlex_node.rb
CHANGED
|
@@ -13,6 +13,7 @@ module A11y
|
|
|
13
13
|
:block_body_codes,
|
|
14
14
|
:call_node,
|
|
15
15
|
:children,
|
|
16
|
+
:configuration,
|
|
16
17
|
:line,
|
|
17
18
|
:tag_name
|
|
18
19
|
)
|
|
@@ -23,7 +24,8 @@ module A11y
|
|
|
23
24
|
call_node: nil, children: [],
|
|
24
25
|
block_body_codes: nil,
|
|
25
26
|
block_has_text_children: false,
|
|
26
|
-
text_content: false
|
|
27
|
+
text_content: false,
|
|
28
|
+
configuration: Configuration.new
|
|
27
29
|
)
|
|
28
30
|
@tag_name = tag_name
|
|
29
31
|
@attributes = attributes
|
|
@@ -33,6 +35,7 @@ module A11y
|
|
|
33
35
|
@block_body_codes = block_body_codes
|
|
34
36
|
@block_has_text_children = block_has_text_children
|
|
35
37
|
@text_content = text_content
|
|
38
|
+
@configuration = configuration
|
|
36
39
|
end
|
|
37
40
|
# rubocop:enable Metrics/ParameterLists
|
|
38
41
|
|
|
@@ -52,30 +55,46 @@ module A11y
|
|
|
52
55
|
@text_content
|
|
53
56
|
end
|
|
54
57
|
|
|
55
|
-
def self.build_tag(
|
|
58
|
+
def self.build_tag(
|
|
59
|
+
call_node, children: [], text_content: false,
|
|
60
|
+
configuration: Configuration.new
|
|
61
|
+
)
|
|
56
62
|
name = call_node.name.to_s
|
|
57
63
|
new(
|
|
58
64
|
tag_name: html_tag_name(name),
|
|
59
65
|
attributes: extract_attributes(call_node),
|
|
60
66
|
line: call_node.location.start_line,
|
|
61
67
|
children: children,
|
|
62
|
-
text_content: text_content
|
|
68
|
+
text_content: text_content,
|
|
69
|
+
configuration: configuration
|
|
63
70
|
)
|
|
64
71
|
end
|
|
65
72
|
|
|
66
73
|
def self.build_helper(
|
|
67
74
|
call_node,
|
|
68
75
|
block_body_codes: nil,
|
|
69
|
-
block_has_text_children: false
|
|
76
|
+
block_has_text_children: false,
|
|
77
|
+
configuration: Configuration.new
|
|
70
78
|
)
|
|
71
79
|
new(
|
|
72
80
|
call_node: CallNode.new(call_node),
|
|
73
81
|
line: call_node.location.start_line,
|
|
74
82
|
block_body_codes: block_body_codes,
|
|
75
|
-
block_has_text_children: block_has_text_children
|
|
83
|
+
block_has_text_children: block_has_text_children,
|
|
84
|
+
configuration: configuration
|
|
76
85
|
)
|
|
77
86
|
end
|
|
78
87
|
|
|
88
|
+
def self.kwarg_class_values(call_node)
|
|
89
|
+
return [] unless call_node.arguments
|
|
90
|
+
|
|
91
|
+
value = kwarg_nodes(call_node).find do |elem|
|
|
92
|
+
kwarg_key(elem.key) == "class"
|
|
93
|
+
end&.value
|
|
94
|
+
|
|
95
|
+
value.is_a?(Prism::StringNode) ? value.unescaped.split : []
|
|
96
|
+
end
|
|
97
|
+
|
|
79
98
|
def self.extract_attributes(call_node)
|
|
80
99
|
return {} unless call_node.arguments
|
|
81
100
|
|
|
@@ -9,8 +9,9 @@ module A11y
|
|
|
9
9
|
class PhlexRunner
|
|
10
10
|
PHLEX_PATTERN = /\bdef\s+view_template\b/
|
|
11
11
|
|
|
12
|
-
def initialize(rules)
|
|
13
|
-
@rules = rules
|
|
12
|
+
def initialize(rules = nil, configuration: Configuration.new)
|
|
13
|
+
@rules = rules || configuration.enabled_rules
|
|
14
|
+
@configuration = configuration
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def run(source, filename:)
|
|
@@ -26,7 +27,7 @@ module A11y
|
|
|
26
27
|
|
|
27
28
|
private
|
|
28
29
|
|
|
29
|
-
attr_reader :rules
|
|
30
|
+
attr_reader :rules, :configuration
|
|
30
31
|
|
|
31
32
|
def walk(node)
|
|
32
33
|
if receiverless_call?(node)
|
|
@@ -48,7 +49,12 @@ module A11y
|
|
|
48
49
|
children = collect_block_children(node.block)
|
|
49
50
|
has_text = tag_block_has_text?(node.block, children)
|
|
50
51
|
check_node(
|
|
51
|
-
PhlexNode.build_tag(
|
|
52
|
+
PhlexNode.build_tag(
|
|
53
|
+
node,
|
|
54
|
+
children: children,
|
|
55
|
+
text_content: has_text,
|
|
56
|
+
configuration: configuration
|
|
57
|
+
)
|
|
52
58
|
)
|
|
53
59
|
end
|
|
54
60
|
|
|
@@ -57,7 +63,8 @@ module A11y
|
|
|
57
63
|
helper = PhlexNode.build_helper(
|
|
58
64
|
node,
|
|
59
65
|
block_body_codes: codes,
|
|
60
|
-
block_has_text_children: has_text
|
|
66
|
+
block_has_text_children: has_text,
|
|
67
|
+
configuration: configuration
|
|
61
68
|
)
|
|
62
69
|
check_node(helper)
|
|
63
70
|
walk_block(node.block)
|
|
@@ -85,10 +92,24 @@ module A11y
|
|
|
85
92
|
kids = collect_block_children(child.block)
|
|
86
93
|
has_text = tag_block_has_text?(child.block, kids)
|
|
87
94
|
tag = PhlexNode.build_tag(
|
|
88
|
-
child, children: kids, text_content: has_text
|
|
95
|
+
child, children: kids, text_content: has_text,
|
|
96
|
+
configuration: configuration
|
|
89
97
|
)
|
|
90
|
-
result << tag
|
|
91
98
|
check_node(tag)
|
|
99
|
+
result << tag unless hidden_wrapper_tag?(child)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def hidden_wrapper_tag?(call_node)
|
|
103
|
+
classes = configuration.hidden_wrapper_classes
|
|
104
|
+
return false if classes.empty?
|
|
105
|
+
|
|
106
|
+
tag_class_values(call_node).any? { |klass| classes.include?(klass) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def tag_class_values(call_node)
|
|
110
|
+
return [] unless call_node.arguments
|
|
111
|
+
|
|
112
|
+
PhlexNode.kwarg_class_values(call_node)
|
|
92
113
|
end
|
|
93
114
|
|
|
94
115
|
def tag_block_has_text?(block, children)
|
|
@@ -114,8 +135,10 @@ module A11y
|
|
|
114
135
|
end
|
|
115
136
|
|
|
116
137
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
138
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
117
139
|
def scan_block_content(node, codes)
|
|
118
140
|
node.child_nodes.compact.each do |child|
|
|
141
|
+
next if tag_call?(child) && hidden_wrapper_tag?(child)
|
|
119
142
|
return true if child.is_a?(Prism::YieldNode)
|
|
120
143
|
return true if tag_call?(child) && child.block
|
|
121
144
|
next if tag_call?(child)
|
|
@@ -125,6 +148,7 @@ module A11y
|
|
|
125
148
|
false
|
|
126
149
|
end
|
|
127
150
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
151
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
128
152
|
|
|
129
153
|
def walk_block(block)
|
|
130
154
|
return unless block.is_a?(Prism::BlockNode)
|
data/lib/a11y/lint/slim_node.rb
CHANGED
|
@@ -6,11 +6,12 @@ module A11y
|
|
|
6
6
|
class SlimNode
|
|
7
7
|
include BlockInspection
|
|
8
8
|
|
|
9
|
-
attr_reader :line
|
|
9
|
+
attr_reader :line, :configuration
|
|
10
10
|
|
|
11
|
-
def initialize(sexp, line:)
|
|
11
|
+
def initialize(sexp, line:, configuration: Configuration.new)
|
|
12
12
|
@sexp = sexp
|
|
13
13
|
@line = line
|
|
14
|
+
@configuration = configuration
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def tag_name
|
|
@@ -90,6 +91,7 @@ module A11y
|
|
|
90
91
|
|
|
91
92
|
def collect_output_codes(sexp)
|
|
92
93
|
return [] unless sexp.is_a?(Array)
|
|
94
|
+
return [] if hidden_wrapper_sexp?(sexp)
|
|
93
95
|
return [sexp[3]] if slim_output_sexp?(sexp)
|
|
94
96
|
|
|
95
97
|
sexp.flat_map { |child| collect_output_codes(child) }
|
|
@@ -101,6 +103,7 @@ module A11y
|
|
|
101
103
|
|
|
102
104
|
def block_text_content?(sexp)
|
|
103
105
|
return false unless sexp.is_a?(Array)
|
|
106
|
+
return false if hidden_wrapper_sexp?(sexp)
|
|
104
107
|
return true if slim_text_sexp?(sexp) || html_tag_sexp?(sexp)
|
|
105
108
|
|
|
106
109
|
sexp.any? { |child| block_text_content?(child) }
|
|
@@ -108,18 +111,69 @@ module A11y
|
|
|
108
111
|
|
|
109
112
|
def text_or_output?(sexp)
|
|
110
113
|
return false unless sexp.is_a?(Array)
|
|
114
|
+
return false if hidden_wrapper_sexp?(sexp)
|
|
111
115
|
return true if slim_text_sexp?(sexp) || slim_output_sexp?(sexp)
|
|
112
116
|
|
|
113
117
|
sexp.any? { |child| text_or_output?(child) }
|
|
114
118
|
end
|
|
115
119
|
|
|
120
|
+
def hidden_wrapper_sexp?(sexp)
|
|
121
|
+
return false unless html_tag_sexp?(sexp)
|
|
122
|
+
return false if configuration.hidden_wrapper_classes.empty?
|
|
123
|
+
|
|
124
|
+
class_values(sexp[3]).any? do |klass|
|
|
125
|
+
configuration.hidden_wrapper_classes.include?(klass)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def class_values(attrs_sexp)
|
|
130
|
+
return [] unless attrs_sexp.is_a?(Array) &&
|
|
131
|
+
attrs_sexp[0] == :html && attrs_sexp[1] == :attrs
|
|
132
|
+
|
|
133
|
+
attrs_sexp[2..].flat_map { |attr| class_values_for_attr(attr) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def class_values_for_attr(attr)
|
|
137
|
+
return [] unless attr.is_a?(Array) &&
|
|
138
|
+
attr[0] == :html && attr[1] == :attr &&
|
|
139
|
+
attr[2] == "class"
|
|
140
|
+
|
|
141
|
+
value = static_class_string(attr[3])
|
|
142
|
+
value ? value.split : []
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Extracts a static class string from the two forms Slim emits:
|
|
146
|
+
# `[:static, "name"]` for class shortcuts (`.popover`) and
|
|
147
|
+
# `[:escape, true, [:slim, :interpolate, "name"]]` for `class="..."`.
|
|
148
|
+
def static_class_string(value_sexp)
|
|
149
|
+
return unless value_sexp.is_a?(Array)
|
|
150
|
+
return static_sexp_value(value_sexp) if value_sexp[0] == :static
|
|
151
|
+
|
|
152
|
+
interpolate_sexp_value(value_sexp)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def static_sexp_value(sexp)
|
|
156
|
+
sexp[1] if sexp[1].is_a?(String)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def interpolate_sexp_value(sexp)
|
|
160
|
+
return unless sexp[0] == :escape && sexp[2].is_a?(Array)
|
|
161
|
+
return unless sexp[2][0] == :slim && sexp[2][1] == :interpolate
|
|
162
|
+
|
|
163
|
+
sexp[2][2] if sexp[2][2].is_a?(String)
|
|
164
|
+
end
|
|
165
|
+
|
|
116
166
|
def slim_text_sexp?(sexp)
|
|
117
167
|
sexp[0] == :slim && sexp[1] == :text
|
|
118
168
|
end
|
|
119
169
|
|
|
120
170
|
def collect_children(sexp)
|
|
121
171
|
return [] unless sexp.is_a?(Array)
|
|
122
|
-
|
|
172
|
+
|
|
173
|
+
if html_tag_sexp?(sexp)
|
|
174
|
+
return [SlimNode.new(sexp, line: @line, configuration: configuration)]
|
|
175
|
+
end
|
|
176
|
+
|
|
123
177
|
return collect_children(sexp[3]) if slim_control_sexp?(sexp)
|
|
124
178
|
return [] unless sexp[0] == :multi
|
|
125
179
|
|
|
@@ -4,8 +4,9 @@ module A11y
|
|
|
4
4
|
module Lint
|
|
5
5
|
# Parses Slim templates and checks them against accessibility rules.
|
|
6
6
|
class SlimRunner
|
|
7
|
-
def initialize(rules)
|
|
8
|
-
@rules = rules
|
|
7
|
+
def initialize(rules = nil, configuration: Configuration.new)
|
|
8
|
+
@rules = rules || configuration.enabled_rules
|
|
9
|
+
@configuration = configuration
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def run(source, filename:)
|
|
@@ -22,13 +23,13 @@ module A11y
|
|
|
22
23
|
|
|
23
24
|
private
|
|
24
25
|
|
|
25
|
-
attr_reader(:rules)
|
|
26
|
+
attr_reader(:rules, :configuration)
|
|
26
27
|
|
|
27
28
|
def walk(sexp)
|
|
28
29
|
return unless node?(sexp)
|
|
29
30
|
|
|
30
31
|
@line += 1 if sexp[0] == :newline
|
|
31
|
-
new_node = SlimNode.new(sexp, line: @line)
|
|
32
|
+
new_node = SlimNode.new(sexp, line: @line, configuration:)
|
|
32
33
|
check_node(new_node) if html_tag?(sexp) || slim_output?(sexp)
|
|
33
34
|
@line += continuation_newlines(sexp)
|
|
34
35
|
sexp.each { |child| walk(child) }
|
data/lib/a11y/lint/version.rb
CHANGED