a11y-lint 0.2.0 → 0.3.1

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: e12c6c589719fa574aecc86ff23ba8f3c2f9b3ae7f5d4da88bae60bbd3035735
4
- data.tar.gz: c107e0d511e57a1cbbbb61ec62a28d3f704bb11e3dbea32851000146f3053a4c
3
+ metadata.gz: 5c000b195d8308dc83a3480df00e9a469e064535305ece969f779ee05b93fc87
4
+ data.tar.gz: cf3ec59befbf3057ff5f1a9d971925656fbf0f0c001e40780202abd862bd67a2
5
5
  SHA512:
6
- metadata.gz: 81a64d2d5a348f5341636124859d31a61f65d6e4fce0d4712bafae17092d7749d5b629aa7f2a7e4269bc54f6e5ce3ff4cbf60225b1ccbcf7f44375104eaa773c
7
- data.tar.gz: bda106347b59bb146e5acc02937eae5d54c0649833c86e86982f6759b128c5fc7b8885b632ee00d6d0848c3746d8d7eb5e4a041b74e1916cc39e6921a57d3ab3
6
+ metadata.gz: ad3e23e799fce7418f9e28224a9ec63120f42d5a04bd27710637df12e278118728af0a3baf47c5fe8b3cfa2a12e6bfbc366d8f334a453b385111b1befc2828df
7
+ data.tar.gz: 8be4c61819523edcbb0a11bb4959ab5834b0552d0fe27be2fc71aa30362dc18eb5ca8f9d25bffa5578f7f77d0af11b5e1716e8429b4c2a865fcd3a243031ede0
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.1] - 2026-03-27
11
+
12
+ ### Fixed
13
+
14
+ - `ImageTagMissingAlt` rule: detect `image_tag` nested inside `link_to` and other helper calls
15
+
16
+ ## [0.3.0] - 2026-03-27
17
+
18
+ ### Added
19
+
20
+ - `ImageTagMissingAlt` rule: detects `image_tag` calls without an `alt` option
21
+
10
22
  ## [0.2.0] - 2026-03-27
11
23
 
12
24
  ### Added
@@ -15,6 +15,12 @@ module A11y
15
15
  @sexp[2]
16
16
  end
17
17
 
18
+ def ruby_code
19
+ return unless @sexp[0] == :slim && @sexp[1] == :output
20
+
21
+ @sexp[3]
22
+ end
23
+
18
24
  def attribute?(name)
19
25
  attributes.key?(name)
20
26
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module A11y
6
+ module Lint
7
+ module Rules
8
+ # Checks that image_tag calls include an alt option (WCAG 1.1.1).
9
+ class ImageTagMissingAlt < Rule
10
+ def check(node)
11
+ return unless an_image_tag_without_an_alt_attribute?(node)
12
+
13
+ "image_tag is missing an alt option (WCAG 1.1.1)"
14
+ end
15
+
16
+ private
17
+
18
+ def an_image_tag_without_an_alt_attribute?(node)
19
+ code = node.ruby_code
20
+ return false unless code
21
+
22
+ sexp = Ripper.sexp(code)
23
+ return false unless sexp
24
+
25
+ call = extract_image_tag_call(sexp)
26
+ call && !alt_key_within?(call)
27
+ end
28
+
29
+ def extract_image_tag_call(sexp)
30
+ return unless sexp.is_a?(Array)
31
+ return sexp if image_tag_call?(sexp)
32
+
33
+ sexp.each do |child|
34
+ result = extract_image_tag_call(child)
35
+ return result if result
36
+ end
37
+
38
+ nil
39
+ end
40
+
41
+ def image_tag_call?(sexp)
42
+ case sexp
43
+ in [:command, [:@ident, "image_tag", *], *] then true
44
+ in [:method_add_arg, [:fcall, [:@ident, "image_tag", *]], *] then true
45
+ else false
46
+ end
47
+ end
48
+
49
+ def alt_key_within?(sexp)
50
+ return true if alt_key?(sexp)
51
+ return false unless sexp.is_a?(Array)
52
+
53
+ sexp.any? { |child| alt_key_within?(child) }
54
+ end
55
+
56
+ def alt_key?(sexp)
57
+ return false unless sexp.is_a?(Array) && sexp[0] == :assoc_new
58
+
59
+ key = sexp[1]
60
+ (key in [:@label, "alt:", *]) ||
61
+ (key in [:string_literal, [:string_content, [:@tstring_content, "alt", *]]])
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -26,7 +26,7 @@ module A11y
26
26
 
27
27
  @line += 1 if sexp[0] == :newline
28
28
  new_node = Node.new(sexp, line: @line)
29
- check_node(new_node) if html_tag?(sexp)
29
+ check_node(new_node) if html_tag?(sexp) || slim_output?(sexp)
30
30
  sexp.each { |child| walk(child) }
31
31
  end
32
32
 
@@ -34,6 +34,10 @@ module A11y
34
34
  sexp[0] == :html && sexp[1] == :tag
35
35
  end
36
36
 
37
+ def slim_output?(sexp)
38
+ sexp[0] == :slim && sexp[1] == :output
39
+ end
40
+
37
41
  def node?(sexp)
38
42
  sexp.is_a?(Array) && !sexp.empty? && sexp[0].is_a?(Symbol)
39
43
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module A11y
4
4
  module Lint
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
  end
data/lib/a11y/lint.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "lint/version"
5
5
  require_relative "lint/offense"
6
6
  require_relative "lint/node"
7
7
  require_relative "lint/rule"
8
+ require_relative "lint/rules/image_tag_missing_alt"
8
9
  require_relative "lint/rules/img_missing_alt"
9
10
  require_relative "lint/runner"
10
11
 
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.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdullah Hashim
@@ -45,6 +45,7 @@ files:
45
45
  - lib/a11y/lint/node.rb
46
46
  - lib/a11y/lint/offense.rb
47
47
  - lib/a11y/lint/rule.rb
48
+ - lib/a11y/lint/rules/image_tag_missing_alt.rb
48
49
  - lib/a11y/lint/rules/img_missing_alt.rb
49
50
  - lib/a11y/lint/runner.rb
50
51
  - lib/a11y/lint/version.rb