rubocop-gusto 11.1.1 → 11.2.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: 5ebf0dc040fba594af70d4d7f12ff478108f0194ff39648ba3d6179ecc9e090f
4
- data.tar.gz: bfdd53b8165f3078c682fb08e57c124acf05996ec6bc4b8543f54b6a94931132
3
+ metadata.gz: bd7d9b067df7edfe822d63daa4c1ffb54b54086cbf80f185ca20888a513d88ec
4
+ data.tar.gz: 4f62f8d399612f2aa26e929b765b33e54324bee3306c27f515b43e15f0c3111f
5
5
  SHA512:
6
- metadata.gz: 68e9932597eab0c974d14c733680f1cba391c1d9203c3eb583737f21902855434a9d7f4a51bad9e15a7fee960098bf3714722063545c4dac70355c3fe212c213
7
- data.tar.gz: d6439f071b56a1f9fb53e22c0964524072c88134b28439c228115217a7b91c3ad4bf82a407ebab4675c966819fa4038fa9cab5b9ada4620c74185c6217373978
6
+ metadata.gz: 5393a0a5937de49f0f70bfd74f401bcabe17a0c24292a9921277d2f81dbf51661e730995b16db0c3de5888ef7e3842236016f156fc94b7446f1eb8693a8beb77
7
+ data.tar.gz: 53114fe101a37c7d0a34f291e038b70c16186c8b4352991d7b7bd03e40b8b293281e62b1adc8fd4d2c6fdeabcc143bd7ff262baaef4c471e5e895f00e843803d
data/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
3
3
  - Remove redundant `Rails: Enabled: true` from `config/rails.yml` (already set by rubocop-rails' own defaults)
4
4
  - Enable `Rails/DefaultScope` cop (disabled by default in rubocop-rails)
5
5
 
6
+ ## [11.2.0](https://github.com/Gusto/rubocop-gusto/compare/v11.1.1...v11.2.0) (2026-06-26)
7
+
8
+
9
+ ### Features
10
+
11
+ * add Gusto/PluckOnSelect cop ([#137](https://github.com/Gusto/rubocop-gusto/issues/137)) ([d87a5cc](https://github.com/Gusto/rubocop-gusto/commit/d87a5ccf45f3f0fca89ed6b6e28cd6f61fc8b023))
12
+ * add Gusto/SmartTodoTeam cop enforcing valid team in TODOs (RR-866) ([#143](https://github.com/Gusto/rubocop-gusto/issues/143)) ([8d36bf5](https://github.com/Gusto/rubocop-gusto/commit/8d36bf58983c32d48ff90b3862eb273f543e60a7))
13
+ * Ensure rubocop-gusto has access to CodeTeams everywhere it runs (RR-880) ([#141](https://github.com/Gusto/rubocop-gusto/issues/141)) ([b2184ba](https://github.com/Gusto/rubocop-gusto/commit/b2184ba3430359e1210206a09746c979f3826ce3))
14
+ * replace Gusto/RailsEnv with upstream Rails/Env ([#106](https://github.com/Gusto/rubocop-gusto/issues/106)) ([7e00033](https://github.com/Gusto/rubocop-gusto/commit/7e0003347dcbed728e4346a7ec2b4f215923a69d))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * use UTF-8 encoding when reading support files in UnreferencedLet ([#136](https://github.com/Gusto/rubocop-gusto/issues/136)) ([651dd25](https://github.com/Gusto/rubocop-gusto/commit/651dd252843a5cfd81dc72bab0abc4b57143c138))
20
+
6
21
  ## [11.1.1](https://github.com/Gusto/rubocop-gusto/compare/v11.1.0...v11.1.1) (2026-06-18)
7
22
 
8
23
 
@@ -67,6 +67,9 @@ Gusto/PerformClassMethod:
67
67
  WorkerModules:
68
68
  - Sidekiq::Worker
69
69
 
70
+ Gusto/PluckOnSelect:
71
+ Description: "Do not use `.pluck` on `.select`. Use one or the other."
72
+
70
73
  Gusto/PolymorphicTypeValidation:
71
74
  Description: 'Ensures that polymorphic relations include a type validation, which is necessary for generating Sorbet types.'
72
75
  Include:
@@ -80,9 +83,6 @@ Gusto/RablExtends:
80
83
  Include:
81
84
  - '**/*.json.rabl'
82
85
 
83
- Gusto/RailsEnv:
84
- Description: 'Use Feature Flags or config instead of `Rails.env`.'
85
-
86
86
  Gusto/RakeConstants:
87
87
  Description: 'Do not define a constant in rake file, because they are sometimes `load`ed, instead of `require`d which can lead to warnings about redefining constants.'
88
88
  Include:
@@ -104,6 +104,10 @@ Gusto/RspecDateTimeMock:
104
104
  Gusto/SidekiqParams:
105
105
  Description: 'Sidekiq perform methods cannot take keyword arguments.'
106
106
 
107
+ Gusto/SmartTodoTeam:
108
+ Description: 'TODO comments must be SmartTodo-formatted and target a valid team (CodeTeams).'
109
+ Enabled: false
110
+
107
111
  Gusto/ToplevelConstants:
108
112
  Description: 'Prevents top-level constants from being defined outside of initializers.'
109
113
  Include:
@@ -133,3 +137,7 @@ Gusto/UsePaintNotColorize:
133
137
 
134
138
  Gusto/VcrRecordings:
135
139
  Description: 'VCR should be set to not record in tests. Use vcr: {record: :none}.'
140
+
141
+ # We extend this via Gusto/SmartTodoTeam; don't let the upstream cop run standalone.
142
+ SmartTodo/SmartTodoCop:
143
+ Enabled: false
data/config/rails.yml CHANGED
@@ -18,6 +18,9 @@ Gusto/DiscouragedGem:
18
18
  Gems:
19
19
  timecop: "Use Rails' time helpers (e.g., freeze_time, travel_to) instead of Timecop."
20
20
 
21
+ Gusto/PluckOnSelect:
22
+ Description: "Do not use `.pluck` on `.select`. Use one or the other."
23
+
21
24
  Performance/DoubleStartEndWith:
22
25
  IncludeActiveSupportAliases: true
23
26
 
@@ -55,6 +58,9 @@ Rails/EnumHash:
55
58
  Include:
56
59
  - '**/app/models/**/*'
57
60
 
61
+ Rails/Env:
62
+ Enabled: true
63
+
58
64
  Rails/Exit:
59
65
  Include:
60
66
  - app/**/*
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Gusto
6
+ # Do not use `.pluck` on `.select`.
7
+ #
8
+ # `.select` returns an ActiveRecord relation with only the selected columns marked for
9
+ # retrieval. `.pluck` returns an array of column values. When chained, `.pluck` is unaware
10
+ # of any directive passed to `.select` (e.g. column aliases or a DISTINCT clause), which
11
+ # can cause unexpected behavior.
12
+ #
13
+ # @example Redundant select
14
+ # # bad
15
+ # User.select(:id).pluck(:id)
16
+ #
17
+ # # good
18
+ # User.pluck(:id)
19
+ #
20
+ # @example Column alias: .pluck raises "Unknown column" because it ignores the alias
21
+ # # bad
22
+ # User.select('id AS id2').pluck('id2')
23
+ #
24
+ # # good: use .select alone if you need the alias
25
+ # User.select(:id, 'id AS id2')
26
+ #
27
+ # # good: use .pluck alone if you don't need the alias
28
+ # User.pluck(:id)
29
+ #
30
+ # @example DISTINCT: .pluck loads all rows, ignoring the DISTINCT from .select
31
+ # # bad
32
+ # User.select('DISTINCT email').pluck(:email)
33
+ #
34
+ # # good
35
+ # User.distinct.pluck(:email)
36
+ #
37
+ class PluckOnSelect < Base
38
+ RESTRICT_ON_SEND = %i(pluck).freeze
39
+ MSG = "Do not use `.pluck` on `.select`."
40
+
41
+ def on_send(node)
42
+ return unless node.receiver
43
+
44
+ receiver_node = node.receiver
45
+ while receiver_node
46
+ if receiver_node.call_type? && receiver_node.method?(:select)
47
+ add_offense(node)
48
+ break
49
+ end
50
+ receiver_node = receiver_node.receiver
51
+ end
52
+ end
53
+ alias_method :on_csend, :on_send
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "smart_todo_cop" # defines RuboCop::Cop::SmartTodo::SmartTodoCop (also requires "smart_todo")
4
+ require "code_teams"
5
+
6
+ module RuboCop
7
+ module Cop
8
+ module Gusto
9
+ # Enforces SmartTodo syntax (via the upstream `SmartTodo/SmartTodoCop`) and,
10
+ # in addition, requires every TODO's `to:` assignee to name a valid team as
11
+ # defined by CodeTeams (`config/teams/**/*.yml`).
12
+ #
13
+ # All of the upstream cop's failure modes are preserved verbatim via `super`.
14
+ # The only additional offense is raised when an otherwise-valid SmartTodo
15
+ # comment is assigned to an unknown team.
16
+ #
17
+ # @example
18
+ # # bad - assigned to an unknown team
19
+ # # TODO(on: date('2025-01-01'), to: 'NotATeam')
20
+ # # Remove this
21
+ #
22
+ # # good - assigned to a team in config/teams
23
+ # # TODO(on: date('2025-01-01'), to: 'Payroll')
24
+ # # Remove this
25
+ class SmartTodoTeam < ::RuboCop::Cop::SmartTodo::SmartTodoCop
26
+ TEAM_HELP = "TODO `to:` must name a valid team (see config/teams). Match the human readable `name:` key (ex: 'Benefits Admin Transfers'), *not* a sluggified form."
27
+
28
+ # @param processed_source [RuboCop::ProcessedSource]
29
+ # @return [void]
30
+ def on_new_investigation
31
+ # Registers every existing SmartTodo offense. RuboCop dedupes offenses by
32
+ # source range (see Cop::Base#add_offense), so any comment flagged here
33
+ # silently swallows the duplicate team offense we might add below.
34
+ super
35
+
36
+ processed_source.comments.each do |comment|
37
+ next unless TODO_PATTERN.match?(comment.text)
38
+
39
+ # `is_a?(String)` guards CodeTeams.find's Sorbet signature, which raises on a
40
+ # non-string. Such assignees are already flagged by `super` (invalid assignee).
41
+ unknown = metadata(comment.text).assignees.select { |assignee| assignee.is_a?(String) && !CodeTeams.find(assignee) }
42
+ next if unknown.empty?
43
+
44
+ add_offense(comment, message: "Unknown team(s): #{unknown.join(', ')}. #{TEAM_HELP}")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -126,7 +126,7 @@ module RuboCop
126
126
  def read_source(path)
127
127
  return "" unless ::File.file?(path)
128
128
 
129
- ::File.read(path)
129
+ ::File.read(path, encoding: "UTF-8")
130
130
  end
131
131
  end
132
132
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Gusto
5
- VERSION = "11.1.1"
5
+ VERSION = "11.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-gusto
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.1.1
4
+ version: 11.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineering
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: code_teams
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: lint_roller
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +107,20 @@ dependencies:
93
107
  - - ">="
94
108
  - !ruby/object:Gem::Version
95
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: smart_todo
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
96
124
  - !ruby/object:Gem::Dependency
97
125
  name: thor
98
126
  requirement: !ruby/object:Gem::Requirement
@@ -138,14 +166,15 @@ files:
138
166
  - lib/rubocop/cop/gusto/no_send.rb
139
167
  - lib/rubocop/cop/gusto/paperclip_or_attachable.rb
140
168
  - lib/rubocop/cop/gusto/perform_class_method.rb
169
+ - lib/rubocop/cop/gusto/pluck_on_select.rb
141
170
  - lib/rubocop/cop/gusto/polymorphic_type_validation.rb
142
171
  - lib/rubocop/cop/gusto/prefer_process_last_status.rb
143
172
  - lib/rubocop/cop/gusto/rabl_extends.rb
144
- - lib/rubocop/cop/gusto/rails_env.rb
145
173
  - lib/rubocop/cop/gusto/rake_constants.rb
146
174
  - lib/rubocop/cop/gusto/regexp_bypass.rb
147
175
  - lib/rubocop/cop/gusto/rspec_date_time_mock.rb
148
176
  - lib/rubocop/cop/gusto/sidekiq_params.rb
177
+ - lib/rubocop/cop/gusto/smart_todo_team.rb
149
178
  - lib/rubocop/cop/gusto/toplevel_constants.rb
150
179
  - lib/rubocop/cop/gusto/unreferenced_let.rb
151
180
  - lib/rubocop/cop/gusto/use_paint_not_colorize.rb
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Gusto
6
- # NOTE: Being pushed upstream here: https://github.com/rubocop/rubocop-rails/pull/1375
7
- # Checks for usage of `Rails.env` which can be replaced with Feature Flags
8
- #
9
- # Although `local?` is a form of an environment-specific check, it is allowed because
10
- # it cannot be used to control overall environment rollout, but it can be helpful to
11
- # distinguish or protect code that is explicitly written to only ever execute in a
12
- # dev or test environment. `local?` is also a form of a feature flag.
13
- #
14
- # @example
15
- #
16
- # # bad
17
- # Rails.env.production? || Rails.env.demo?
18
- #
19
- # # good
20
- # if FeatureFlag.enabled?(:new_feature)
21
- # # new feature code
22
- # end
23
- #
24
- # # good
25
- # raise unless Rails.env.local?
26
- #
27
- # # good
28
- # abort ("The Rails environment is running in production mode!") unless Rails.env.local?
29
- #
30
- class RailsEnv < Base
31
- # This allow list is derived from:
32
- # (Rails.env.methods - Object.instance_methods).select { |m| m.to_s.end_with?('?') }
33
- # and then removing the environment specific methods like development?, test?, production?
34
- ALLOWED_LIST = Set.new(
35
- %i(
36
- unicode_normalized?
37
- exclude?
38
- empty?
39
- acts_like_string?
40
- include?
41
- is_utf8?
42
- casecmp?
43
- match?
44
- starts_with?
45
- ends_with?
46
- start_with?
47
- end_with?
48
- valid_encoding?
49
- ascii_only?
50
- between?
51
- local?
52
- )
53
- ).freeze
54
- MSG = "Use Feature Flags or config instead of `Rails.env`."
55
- RESTRICT_ON_SEND = %i(env).freeze
56
-
57
- # @!method prohibited_rails_env?(node)
58
- def_node_matcher :prohibited_rails_env?, <<~PATTERN
59
- (send
60
- (send (const _ :Rails) :env)
61
- #prohibited_predicate?
62
- )
63
- PATTERN
64
-
65
- def on_send(node)
66
- add_offense(node.parent) if prohibited_rails_env?(node.parent)
67
- end
68
-
69
- private
70
-
71
- def prohibited_predicate?(name)
72
- name.to_s.end_with?("?") && !ALLOWED_LIST.include?(name)
73
- end
74
- end
75
- end
76
- end
77
- end