rubocop-gusto 10.6.0 → 10.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8312e221e4e60856991b5771855411eda476845804f95a1b890944880a72c107
4
- data.tar.gz: 48247a74fe3523f5d813fc1b36fe9f56782c166347bd1f45997876047d5a9bf4
3
+ metadata.gz: a5ad5381e46b28ac74b61ba851caa498b964314ad364503d887bdb44908eace6
4
+ data.tar.gz: 9dc15d64da91e12a36ff62f122ca7bc57a47251f2e6e12dc128fe27d7d4d6419
5
5
  SHA512:
6
- metadata.gz: b2b908c4b1f5f921371e6368956f57bce33995ab5127f346a59d4958909fd03469a8efde3ab035b52457c67bae2f8fc14efcb3c83250810ade31fe872b60948f
7
- data.tar.gz: 5e0ce03d855c35c66a70fd0bbd3f940d3661e6d665fcac97d5c73c4fd57b10a3b5842b170ac9838d46fe6cec9ff042aedcc2e0950dfcfd231e035d3077fd9c27
6
+ metadata.gz: ecb903c72b7a302c2c9627d4e30d77fcbb5e736d8d82e581cf810e3c64ef3a2ccddd57200e204d22092a194d50773ba4181d0ff7172358ac429ee96fed6f9f2d
7
+ data.tar.gz: ab58cc3ea05f13ba3ad600921ef6cf128e5e848d9a4762aac1112ff4cd493636cf69a13f76c59b8c961f464cd9a9ddad43b358eed6d56791e18230817cb91def
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  ## Pending
2
2
 
3
3
 
4
+ ## 10.7.0
5
+
6
+ - Improve `Rack/LowercaseHeaderKeys` for Rack 3 migration
7
+ - Change `Style/StringLiterals` `EnforcedStyle` from `always` to `always_true`
8
+
4
9
  ## 10.6.0
5
10
 
6
11
  - Add `Rack/LowercaseHeaderKeys` cop to detect and autocorrect uppercase HTTP response header keys
data/config/default.yml CHANGED
@@ -430,14 +430,19 @@ RSpec/SubjectStub:
430
430
  Enabled: false
431
431
 
432
432
  Rack/LowercaseHeaderKeys:
433
- Description: 'HTTP response header keys should be lowercase for consistency and compatibility with HTTP/2.'
433
+ Description: 'HTTP response header keys should be lowercase for HTTP/2 and modern web standards compatibility.'
434
434
  Enabled: true
435
435
  Include:
436
436
  - 'lib/middleware/**/*.rb'
437
437
  - 'app/middleware/**/*.rb'
438
438
  - 'app/controllers/**/*.rb'
439
+ - 'app/public/**/*controller*.rb'
440
+ - 'app/helpers/**/*.rb'
439
441
  - 'packs/**/middleware/**/*.rb'
440
442
  - 'packs/**/controllers/**/*.rb'
443
+ - 'packs/**/app/public/**/*controller*.rb'
444
+ - 'packs/**/app/helpers/**/*.rb'
445
+ - 'packs/**/lib/**/*_app.rb'
441
446
  - 'components/**/middleware/**/*.rb'
442
447
  - 'components/**/controllers/**/*.rb'
443
448
  - 'config/initializers/**/*.rb'
@@ -565,7 +570,7 @@ Style/FormatStringToken:
565
570
  EnforcedStyle: template
566
571
 
567
572
  Style/FrozenStringLiteralComment:
568
- EnforcedStyle: always
573
+ EnforcedStyle: always_true
569
574
  Enabled: true
570
575
 
571
576
  # This can make lines longer and impair readability
@@ -4,91 +4,111 @@ module RuboCop
4
4
  module Cop
5
5
  module Rack
6
6
  # Detects HTTP response headers with uppercase characters.
7
- # HTTP response header keys should be lowercase for consistency
8
- # and compatibility with HTTP/2 and modern web standards.
7
+ # HTTP response header keys should be lowercase for compatibility
8
+ # with HTTP/2 and modern web standards.
9
+ #
10
+ # HTTP/2 (RFC 9113) requires all header field names to be lowercase.
11
+ # Using lowercase keys ensures compatibility across HTTP versions
12
+ # and Rack implementations.
9
13
  #
10
14
  # @example
11
15
  # # bad
12
16
  # headers['Content-Type'] = 'application/json'
13
17
  # response.headers['Location'] = '/redirect'
18
+ # response.set_header('X-Custom', 'value')
19
+ # response.headers['Content-Security-Policy'] += policy
14
20
  #
15
21
  # # good
16
22
  # headers['content-type'] = 'application/json'
17
23
  # response.headers['location'] = '/redirect'
24
+ # response.set_header('x-custom', 'value')
25
+ # response.headers['content-security-policy'] += policy
18
26
  #
19
27
  class LowercaseHeaderKeys < Base
20
28
  extend AutoCorrector
21
29
 
22
30
  MSG = "HTTP response header keys should be lowercase. Use `%{downcased}` instead of `%{original}`."
23
- RESTRICT_ON_SEND = %i([]=).freeze
24
-
25
- # Known HTTP headers (case-insensitive check)
26
- KNOWN_HEADERS = Set.new(
27
- %w(
28
- Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges
29
- Access-Control-Allow-Credentials Access-Control-Allow-Headers
30
- Access-Control-Allow-Methods Access-Control-Allow-Origin
31
- Access-Control-Allow-Private-Network Access-Control-Expose-Headers
32
- Access-Control-Max-Age Access-Control-Request-Headers
33
- Access-Control-Request-Method Age Allow Authorization
34
- Cache-Control Connection Content-Disposition Content-Encoding
35
- Content-Language Content-Length Content-Location Content-Range
36
- Content-Security-Policy Content-Security-Policy-Report-Only
37
- Content-Type Cookie Date ETag Expect
38
- Expires Forwarded From Host If-Match If-Modified-Since
39
- If-None-Match If-Range If-Unmodified-Since Last-Modified
40
- Link Location Max-Forwards Origin Pragma Proxy-Authenticate
41
- Proxy-Authorization Range Referer Referrer-Policy Retry-After
42
- Server Set-Cookie SOAPAction Strict-Transport-Security TE Trailer
43
- Transfer-Encoding Upgrade User-Agent Vary Via Warning
44
- WWW-Authenticate X-Content-Type-Options X-Frame-Options
45
- X-XSS-Protection X-Forwarded-For X-Forwarded-Host X-Forwarded-Proto
46
- X-Real-IP X-Request-ID X-Request-Start X-Requested-With
47
- ).map(&:downcase)
48
- ).freeze
31
+ RESTRICT_ON_SEND = %i([]= [] set_header get_header delete_header has_header?).freeze
32
+
33
+ # @!method response_header_method?(node)
34
+ def_node_matcher :response_header_method?, <<~PATTERN
35
+ (send
36
+ {
37
+ (send nil? :response)
38
+ (lvar :response)
39
+ (self)
40
+ }
41
+ {:set_header :get_header :delete_header :has_header?}
42
+ (str _)
43
+ ...
44
+ )
45
+ PATTERN
49
46
 
50
47
  def on_send(node)
51
- return unless headers_assignment?(node)
48
+ if node.method?(:[]=) || node.method?(:[])
49
+ check_bracket_access(node)
50
+ elsif response_header_method?(node)
51
+ check_header_method(node)
52
+ end
53
+ end
54
+ alias_method :on_csend, :on_send
52
55
 
53
- key_node = node.first_argument
54
- return unless key_node.str_type?
56
+ # Handle compound assignment: headers['Key'] += val
57
+ # Ruby parses this as an op_asgn node, not a []= send
58
+ def on_op_asgn(node)
59
+ lhs = node.children[0]
60
+ return unless lhs.send_type? && lhs.method?(:[])
55
61
 
56
- key_value = key_node.value
57
- return unless uppercase_known_header?(key_value)
62
+ receiver = lhs.receiver
63
+ return unless receiver.send_type? && response_headers_receiver?(receiver)
58
64
 
59
- add_offense_for_header(key_node, key_value)
65
+ key_node = lhs.first_argument
66
+ return unless key_node.str_type?
67
+
68
+ check_key(key_node)
60
69
  end
61
- alias_method :on_csend, :on_send
62
70
 
63
71
  private
64
72
 
65
- def uppercase_known_header?(key)
66
- return false if key.empty?
67
- return false if key == key.downcase
73
+ def check_bracket_access(node)
74
+ receiver = node.receiver
75
+ return unless receiver.send_type? && response_headers_receiver?(receiver)
76
+
77
+ key_node = node.first_argument
78
+ return unless key_node.str_type?
68
79
 
69
- KNOWN_HEADERS.include?(key.downcase)
80
+ check_key(key_node)
70
81
  end
71
82
 
72
- # Matches:
73
- # headers['...'] = value (bare method call in controller)
74
- # response.headers['...'] = value
75
- # Does NOT match:
76
- # conn.headers, request.headers, client.headers, etc.
77
- def headers_assignment?(node)
78
- receiver = node.receiver
79
- # RESTRICT_ON_SEND ensures we only see []=, which always has a receiver
80
- return false unless receiver.send_type?
83
+ def check_header_method(node)
84
+ key_node = node.first_argument
85
+ check_key(key_node)
86
+ end
81
87
 
82
- headers_method_receiver?(receiver)
88
+ def check_key(key_node)
89
+ key_value = key_node.value
90
+ return if key_value.empty?
91
+ return if key_value == key_value.downcase
92
+
93
+ add_offense_for_header(key_node, key_value)
83
94
  end
84
95
 
85
- def headers_method_receiver?(receiver)
96
+ # Matches headers receivers that are response-related:
97
+ # headers (bare method call in controller)
98
+ # response.headers
99
+ # self.headers
100
+ # Does NOT match:
101
+ # conn.headers, request.headers, client.headers, etc.
102
+ def response_headers_receiver?(receiver)
86
103
  return false unless receiver.method?(:headers)
87
104
 
88
105
  inner = receiver.receiver
89
106
  if inner.nil?
90
107
  # Bare `headers` method call (Rails controller helper)
91
108
  true
109
+ elsif inner.self_type?
110
+ # `self.headers`
111
+ true
92
112
  elsif inner.send_type? && inner.receiver.nil? && inner.method?(:response)
93
113
  # `response.headers`
94
114
  true
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Gusto
5
- VERSION = "10.6.0"
5
+ VERSION = "10.7.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-gusto
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.6.0
4
+ version: 10.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineering
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-02-25 00:00:00.000000000 Z
10
+ date: 2026-03-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: lint_roller