rubocop-gusto 10.3.0 → 10.6.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 +15 -0
- data/config/default.yml +32 -4
- data/config/rails.yml +8 -0
- data/lib/rubocop/cop/gusto/bootsnap_load_file.rb +1 -0
- data/lib/rubocop/cop/gusto/discouraged_gem.rb +50 -0
- data/lib/rubocop/cop/rack/lowercase_header_keys.rb +114 -0
- data/lib/rubocop/gusto/config_yml.rb +3 -2
- data/lib/rubocop/gusto/version.rb +1 -1
- metadata +5 -4
- data/lib/rubocop/cop/gusto/object_in.rb +0 -36
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8312e221e4e60856991b5771855411eda476845804f95a1b890944880a72c107
|
|
4
|
+
data.tar.gz: 48247a74fe3523f5d813fc1b36fe9f56782c166347bd1f45997876047d5a9bf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2b908c4b1f5f921371e6368956f57bce33995ab5127f346a59d4958909fd03469a8efde3ab035b52457c67bae2f8fc14efcb3c83250810ade31fe872b60948f
|
|
7
|
+
data.tar.gz: 5e0ce03d855c35c66a70fd0bbd3f940d3661e6d665fcac97d5c73c4fd57b10a3b5842b170ac9838d46fe6cec9ff042aedcc2e0950dfcfd231e035d3077fd9c27
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## Pending
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## 10.6.0
|
|
5
|
+
|
|
6
|
+
- Add `Rack/LowercaseHeaderKeys` cop to detect and autocorrect uppercase HTTP response header keys
|
|
7
|
+
- Enable Style/StringLiterals with double quotes enforced
|
|
8
|
+
|
|
9
|
+
## 10.5.0
|
|
10
|
+
|
|
11
|
+
- Delete Object#in? cop
|
|
12
|
+
|
|
13
|
+
## 10.4.0
|
|
14
|
+
|
|
15
|
+
- Add Gusto/DiscouragedGem cop with `timecop` as the first discouraged gem
|
|
16
|
+
- Update Gusto/PolymorphicTypeValidation settings to be scoped to `**/models/*.rb`
|
|
17
|
+
|
|
3
18
|
## 10.3.0
|
|
4
19
|
|
|
5
20
|
- Add Gusto/RspecDateTimeMock cop
|
data/config/default.yml
CHANGED
|
@@ -49,6 +49,11 @@ Gusto/DatadogConstant:
|
|
|
49
49
|
- '**/spec/**/*'
|
|
50
50
|
Description: 'Do not call Datadog directly, use an appropriate wrapper library.'
|
|
51
51
|
|
|
52
|
+
Gusto/DiscouragedGem:
|
|
53
|
+
Description: 'Flags installation of discouraged gems in Gemfiles and gemspecs.'
|
|
54
|
+
Enabled: false
|
|
55
|
+
Gems: {}
|
|
56
|
+
|
|
52
57
|
Gusto/ExecuteMigration:
|
|
53
58
|
Description: "Don't use `execute` in migrations. Use a backfill rake task instead."
|
|
54
59
|
Include:
|
|
@@ -72,10 +77,8 @@ Gusto/NoRescueErrorMessageChecking:
|
|
|
72
77
|
|
|
73
78
|
Gusto/NoSend:
|
|
74
79
|
Description: 'Do not call a private method via `__send__`.'
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Description: 'Use `Range#cover?` instead of `Object#in?`.'
|
|
78
|
-
Safe: false
|
|
80
|
+
Exclude:
|
|
81
|
+
- '**/spec/**/*'
|
|
79
82
|
|
|
80
83
|
Gusto/PaperclipOrAttachable:
|
|
81
84
|
Description: 'No more new paperclip or Attachable are allowed. Use ActiveStorage instead.'
|
|
@@ -89,6 +92,8 @@ Gusto/PerformClassMethod:
|
|
|
89
92
|
|
|
90
93
|
Gusto/PolymorphicTypeValidation:
|
|
91
94
|
Description: 'Ensures that polymorphic relations include a type validation, which is necessary for generating Sorbet types.'
|
|
95
|
+
Include:
|
|
96
|
+
- '**/models/**/*.rb'
|
|
92
97
|
|
|
93
98
|
Gusto/PreferProcessLastStatus:
|
|
94
99
|
Description: 'Prefer using `Process.last_status` instead of the global variables: `$?` and `$CHILD_STATUS`.'
|
|
@@ -424,6 +429,21 @@ RSpec/StubbedMock:
|
|
|
424
429
|
RSpec/SubjectStub:
|
|
425
430
|
Enabled: false
|
|
426
431
|
|
|
432
|
+
Rack/LowercaseHeaderKeys:
|
|
433
|
+
Description: 'HTTP response header keys should be lowercase for consistency and compatibility with HTTP/2.'
|
|
434
|
+
Enabled: true
|
|
435
|
+
Include:
|
|
436
|
+
- 'lib/middleware/**/*.rb'
|
|
437
|
+
- 'app/middleware/**/*.rb'
|
|
438
|
+
- 'app/controllers/**/*.rb'
|
|
439
|
+
- 'packs/**/middleware/**/*.rb'
|
|
440
|
+
- 'packs/**/controllers/**/*.rb'
|
|
441
|
+
- 'components/**/middleware/**/*.rb'
|
|
442
|
+
- 'components/**/controllers/**/*.rb'
|
|
443
|
+
- 'config/initializers/**/*.rb'
|
|
444
|
+
Exclude:
|
|
445
|
+
- '**/spec/**/*'
|
|
446
|
+
|
|
427
447
|
Rake/ClassDefinitionInTask:
|
|
428
448
|
Enabled: false
|
|
429
449
|
|
|
@@ -481,6 +501,10 @@ Style/Alias:
|
|
|
481
501
|
Enabled: true
|
|
482
502
|
EnforcedStyle: prefer_alias_method
|
|
483
503
|
|
|
504
|
+
Style/ArgumentsForwarding:
|
|
505
|
+
# This is incompatible with Sorbet
|
|
506
|
+
Enabled: false
|
|
507
|
+
|
|
484
508
|
Style/AsciiComments:
|
|
485
509
|
Enabled: true
|
|
486
510
|
|
|
@@ -569,6 +593,10 @@ Style/IfUnlessModifier:
|
|
|
569
593
|
Style/ImplicitRuntimeError:
|
|
570
594
|
Enabled: false
|
|
571
595
|
|
|
596
|
+
Style/ItBlockParameter:
|
|
597
|
+
Enabled: true
|
|
598
|
+
EnforcedStyle: only_numbered_parameters
|
|
599
|
+
|
|
572
600
|
Style/Lambda:
|
|
573
601
|
EnforcedStyle: literal
|
|
574
602
|
|
data/config/rails.yml
CHANGED
|
@@ -17,6 +17,14 @@ AllCops:
|
|
|
17
17
|
- 'db/**/*schema.rb'
|
|
18
18
|
- 'db/seeds{.rb,/**/*}'
|
|
19
19
|
|
|
20
|
+
Gusto/DiscouragedGem:
|
|
21
|
+
Enabled: true
|
|
22
|
+
Include:
|
|
23
|
+
- '**/*.gemspec'
|
|
24
|
+
- '**/Gemfile'
|
|
25
|
+
Gems:
|
|
26
|
+
timecop: "Use Rails' time helpers (e.g., freeze_time, travel_to) instead of Timecop."
|
|
27
|
+
|
|
20
28
|
Performance/DoubleStartEndWith:
|
|
21
29
|
IncludeActiveSupportAliases: true
|
|
22
30
|
|
|
@@ -33,6 +33,7 @@ module RuboCop
|
|
|
33
33
|
add_offense(node, message: "Use #{constant_node.source}.load_file(#{file_path_node.source}) to improve load time with bootsnap")
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
|
+
alias_method :on_itblock, :on_block
|
|
36
37
|
alias_method :on_numblock, :on_block
|
|
37
38
|
|
|
38
39
|
def on_send(node)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Gusto
|
|
6
|
+
# Flags installation of discouraged gems (e.g., timecop) in Gemfiles and gemspecs.
|
|
7
|
+
#
|
|
8
|
+
# Configuration:
|
|
9
|
+
# Gems:
|
|
10
|
+
# timecop: "Use Rails' time helpers (e.g., freeze_time, travel_to) instead of Timecop."
|
|
11
|
+
#
|
|
12
|
+
# This cop is intended to be enabled in Rails projects via config/rails.yml.
|
|
13
|
+
class DiscouragedGem < Base
|
|
14
|
+
MSG = "Avoid using the '%{gem}' gem. %{advice}"
|
|
15
|
+
|
|
16
|
+
RESTRICT_ON_SEND = %i(gem add_dependency add_development_dependency).freeze
|
|
17
|
+
|
|
18
|
+
def on_send(node)
|
|
19
|
+
check_gem_usage(node)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def check_gem_usage(node)
|
|
25
|
+
return unless node.first_argument&.type?(:str, :sym)
|
|
26
|
+
return unless discouraged_gems.include?(node.first_argument.value.to_s)
|
|
27
|
+
|
|
28
|
+
add_offense(node, message: message_for(node.first_argument.value.to_s))
|
|
29
|
+
# No autocorrect: removing dependencies is a project decision.
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def discouraged_gems
|
|
33
|
+
@discouraged_gems ||= gems_config.keys.map(&:to_s)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def message_for(gem)
|
|
37
|
+
format(MSG, gem: gem, advice: advice_for(gem))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def advice_for(gem)
|
|
41
|
+
gems_config[gem]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def gems_config
|
|
45
|
+
cop_config["Gems"] || {}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Rack
|
|
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.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad
|
|
12
|
+
# headers['Content-Type'] = 'application/json'
|
|
13
|
+
# response.headers['Location'] = '/redirect'
|
|
14
|
+
#
|
|
15
|
+
# # good
|
|
16
|
+
# headers['content-type'] = 'application/json'
|
|
17
|
+
# response.headers['location'] = '/redirect'
|
|
18
|
+
#
|
|
19
|
+
class LowercaseHeaderKeys < Base
|
|
20
|
+
extend AutoCorrector
|
|
21
|
+
|
|
22
|
+
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
|
|
49
|
+
|
|
50
|
+
def on_send(node)
|
|
51
|
+
return unless headers_assignment?(node)
|
|
52
|
+
|
|
53
|
+
key_node = node.first_argument
|
|
54
|
+
return unless key_node.str_type?
|
|
55
|
+
|
|
56
|
+
key_value = key_node.value
|
|
57
|
+
return unless uppercase_known_header?(key_value)
|
|
58
|
+
|
|
59
|
+
add_offense_for_header(key_node, key_value)
|
|
60
|
+
end
|
|
61
|
+
alias_method :on_csend, :on_send
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def uppercase_known_header?(key)
|
|
66
|
+
return false if key.empty?
|
|
67
|
+
return false if key == key.downcase
|
|
68
|
+
|
|
69
|
+
KNOWN_HEADERS.include?(key.downcase)
|
|
70
|
+
end
|
|
71
|
+
|
|
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?
|
|
81
|
+
|
|
82
|
+
headers_method_receiver?(receiver)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def headers_method_receiver?(receiver)
|
|
86
|
+
return false unless receiver.method?(:headers)
|
|
87
|
+
|
|
88
|
+
inner = receiver.receiver
|
|
89
|
+
if inner.nil?
|
|
90
|
+
# Bare `headers` method call (Rails controller helper)
|
|
91
|
+
true
|
|
92
|
+
elsif inner.send_type? && inner.receiver.nil? && inner.method?(:response)
|
|
93
|
+
# `response.headers`
|
|
94
|
+
true
|
|
95
|
+
elsif inner.lvar_type? && inner.children.first == :response
|
|
96
|
+
# `response.headers` where response is a local var
|
|
97
|
+
true
|
|
98
|
+
else
|
|
99
|
+
false
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def add_offense_for_header(node, key_value)
|
|
104
|
+
downcased = key_value.downcase
|
|
105
|
+
message = format(MSG, downcased: downcased, original: key_value)
|
|
106
|
+
|
|
107
|
+
add_offense(node, message: message) do |corrector|
|
|
108
|
+
corrector.replace(node, "'#{downcased}'")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -18,7 +18,7 @@ module RuboCop
|
|
|
18
18
|
|
|
19
19
|
# @param [String] file_path the path to the .rubocop.yml file
|
|
20
20
|
def self.load_file(file_path = ".rubocop.yml")
|
|
21
|
-
new(File.readlines(file_path))
|
|
21
|
+
new(File.readlines(file_path, encoding: "UTF-8"))
|
|
22
22
|
rescue Errno::ENOENT
|
|
23
23
|
new([])
|
|
24
24
|
end
|
|
@@ -75,9 +75,10 @@ module RuboCop
|
|
|
75
75
|
|
|
76
76
|
def sort!
|
|
77
77
|
# Sort the preamble chunks by our preferred order, falling back to key name
|
|
78
|
+
# Comment-only chunks (nil key) sort to the end with "ZZZZZ"
|
|
78
79
|
preamble.sort_by! do |chunk|
|
|
79
80
|
key = chunk_name(chunk)
|
|
80
|
-
PREAMBLE_KEYS.index(key)&.to_s || key
|
|
81
|
+
PREAMBLE_KEYS.index(key)&.to_s || key || "ZZZZZ"
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
# Sort the cops by their key name, putting comments at the top
|
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.
|
|
4
|
+
version: 10.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gusto Engineering
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-02-25 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: lint_roller
|
|
@@ -125,13 +125,13 @@ files:
|
|
|
125
125
|
- lib/rubocop-gusto.rb
|
|
126
126
|
- lib/rubocop/cop/gusto/bootsnap_load_file.rb
|
|
127
127
|
- lib/rubocop/cop/gusto/datadog_constant.rb
|
|
128
|
+
- lib/rubocop/cop/gusto/discouraged_gem.rb
|
|
128
129
|
- lib/rubocop/cop/gusto/execute_migration.rb
|
|
129
130
|
- lib/rubocop/cop/gusto/factory_classes_or_modules.rb
|
|
130
131
|
- lib/rubocop/cop/gusto/min_by_max_by.rb
|
|
131
132
|
- lib/rubocop/cop/gusto/no_metaprogramming.rb
|
|
132
133
|
- lib/rubocop/cop/gusto/no_rescue_error_message_checking.rb
|
|
133
134
|
- lib/rubocop/cop/gusto/no_send.rb
|
|
134
|
-
- lib/rubocop/cop/gusto/object_in.rb
|
|
135
135
|
- lib/rubocop/cop/gusto/paperclip_or_attachable.rb
|
|
136
136
|
- lib/rubocop/cop/gusto/perform_class_method.rb
|
|
137
137
|
- lib/rubocop/cop/gusto/polymorphic_type_validation.rb
|
|
@@ -147,6 +147,7 @@ files:
|
|
|
147
147
|
- lib/rubocop/cop/gusto/vcr_recordings.rb
|
|
148
148
|
- lib/rubocop/cop/internal_affairs/assignment_first.rb
|
|
149
149
|
- lib/rubocop/cop/internal_affairs/require_restrict_on_send.rb
|
|
150
|
+
- lib/rubocop/cop/rack/lowercase_header_keys.rb
|
|
150
151
|
- lib/rubocop/gusto.rb
|
|
151
152
|
- lib/rubocop/gusto/cli.rb
|
|
152
153
|
- lib/rubocop/gusto/config_yml.rb
|
|
@@ -173,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
173
174
|
- !ruby/object:Gem::Version
|
|
174
175
|
version: '0'
|
|
175
176
|
requirements: []
|
|
176
|
-
rubygems_version: 3.
|
|
177
|
+
rubygems_version: 3.6.2
|
|
177
178
|
specification_version: 4
|
|
178
179
|
summary: A gem for sharing gusto rubocop rules
|
|
179
180
|
test_files: []
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Cop
|
|
5
|
-
module Gusto
|
|
6
|
-
# Identifies uses of `Object#in?`, which iterates over each
|
|
7
|
-
# item in a `Range` to see if a specified item is there. In contrast,
|
|
8
|
-
# `Range#cover?` simply compares the target item with the beginning and
|
|
9
|
-
# end points of the `Range`. In a great majority of cases, this is what
|
|
10
|
-
# is wanted.
|
|
11
|
-
#
|
|
12
|
-
# @safety
|
|
13
|
-
# This cop is unsafe. Here is an example of a case where `Range#cover?`
|
|
14
|
-
# may not provide the desired result:
|
|
15
|
-
#
|
|
16
|
-
# ('a'..'z').cover?('yellow') # => true
|
|
17
|
-
#
|
|
18
|
-
class ObjectIn < Base
|
|
19
|
-
MSG = "Use `Range#cover?` instead of `Object#in?`."
|
|
20
|
-
RESTRICT_ON_SEND = [:in?].freeze
|
|
21
|
-
|
|
22
|
-
# @!method object_in(node)
|
|
23
|
-
def_node_matcher :object_in, <<-PATTERN
|
|
24
|
-
(call _ :in? {range (begin range)})
|
|
25
|
-
PATTERN
|
|
26
|
-
|
|
27
|
-
def on_send(node)
|
|
28
|
-
return unless object_in(node)
|
|
29
|
-
|
|
30
|
-
add_offense(node)
|
|
31
|
-
end
|
|
32
|
-
alias_method :on_csend, :on_send
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|