rubocop-gusto 11.1.1 → 11.3.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: 63e4d5c0598bcf51b28b73659d6ef7d2441d350b425f8d34ac4b02dc7eaaf41c
4
+ data.tar.gz: 0ac1b025edbc7f1add82364538074752bd47c2272d8b5fd59fc3aa90db9b052c
5
5
  SHA512:
6
- metadata.gz: 68e9932597eab0c974d14c733680f1cba391c1d9203c3eb583737f21902855434a9d7f4a51bad9e15a7fee960098bf3714722063545c4dac70355c3fe212c213
7
- data.tar.gz: d6439f071b56a1f9fb53e22c0964524072c88134b28439c228115217a7b91c3ad4bf82a407ebab4675c966819fa4038fa9cab5b9ada4620c74185c6217373978
6
+ metadata.gz: 827456db6259cd5414df4d864b65b360cf67f1ba6f8d047d4e4d5bc3374fe42f9220d7012bbf8b1f84fcbac1b5b6d675ea9bc42a77a26ef59f86a0179b92800d
7
+ data.tar.gz: 7543bc23e8c2aaf3747bbfe9e661fb0c83c1a62d23dad639dc95bbc60a7ecd913dce2bc3c8057f35183b8a9367105ee2b6e64821513f50d6365aa74e1935b776
data/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
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.3.0](https://github.com/Gusto/rubocop-gusto/compare/v11.2.0...v11.3.0) (2026-06-29)
7
+
8
+
9
+ ### Features
10
+
11
+ * add Gusto/FeatureFlagConstants cop (RR-890) ([#144](https://github.com/Gusto/rubocop-gusto/issues/144)) ([9058e0e](https://github.com/Gusto/rubocop-gusto/commit/9058e0e3055445a6be1818f2c142013239773bb5))
12
+
13
+ ## [11.2.0](https://github.com/Gusto/rubocop-gusto/compare/v11.1.1...v11.2.0) (2026-06-26)
14
+
15
+
16
+ ### Features
17
+
18
+ * add Gusto/PluckOnSelect cop ([#137](https://github.com/Gusto/rubocop-gusto/issues/137)) ([d87a5cc](https://github.com/Gusto/rubocop-gusto/commit/d87a5ccf45f3f0fca89ed6b6e28cd6f61fc8b023))
19
+ * 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))
20
+ * 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))
21
+ * 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))
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * 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))
27
+
6
28
  ## [11.1.1](https://github.com/Gusto/rubocop-gusto/compare/v11.1.0...v11.1.1) (2026-06-18)
7
29
 
8
30
 
@@ -43,6 +43,9 @@ Gusto/FactoryClassesOrModules:
43
43
  Include:
44
44
  - 'spec/**/factories/*.rb'
45
45
 
46
+ Gusto/FeatureFlagConstants:
47
+ Description: 'FeatureFlag keys should be constants, not strings.'
48
+
46
49
  Gusto/MinByMaxBy:
47
50
  Description: 'Checks for the use of `min` or `max` with a proc. Corrects to `min_by` or `max_by`.'
48
51
  Safe: false
@@ -67,6 +70,9 @@ Gusto/PerformClassMethod:
67
70
  WorkerModules:
68
71
  - Sidekiq::Worker
69
72
 
73
+ Gusto/PluckOnSelect:
74
+ Description: "Do not use `.pluck` on `.select`. Use one or the other."
75
+
70
76
  Gusto/PolymorphicTypeValidation:
71
77
  Description: 'Ensures that polymorphic relations include a type validation, which is necessary for generating Sorbet types.'
72
78
  Include:
@@ -80,9 +86,6 @@ Gusto/RablExtends:
80
86
  Include:
81
87
  - '**/*.json.rabl'
82
88
 
83
- Gusto/RailsEnv:
84
- Description: 'Use Feature Flags or config instead of `Rails.env`.'
85
-
86
89
  Gusto/RakeConstants:
87
90
  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
91
  Include:
@@ -104,6 +107,10 @@ Gusto/RspecDateTimeMock:
104
107
  Gusto/SidekiqParams:
105
108
  Description: 'Sidekiq perform methods cannot take keyword arguments.'
106
109
 
110
+ Gusto/SmartTodoTeam:
111
+ Description: 'TODO comments must be SmartTodo-formatted and target a valid team (CodeTeams).'
112
+ Enabled: false
113
+
107
114
  Gusto/ToplevelConstants:
108
115
  Description: 'Prevents top-level constants from being defined outside of initializers.'
109
116
  Include:
@@ -133,3 +140,7 @@ Gusto/UsePaintNotColorize:
133
140
 
134
141
  Gusto/VcrRecordings:
135
142
  Description: 'VCR should be set to not record in tests. Use vcr: {record: :none}.'
143
+
144
+ # We extend this via Gusto/SmartTodoTeam; don't let the upstream cop run standalone.
145
+ SmartTodo/SmartTodoCop:
146
+ 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,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Gusto
6
+ # Enforces that `FeatureFlag.active?` is called with a constant rather than a
7
+ # string literal. Defining flag keys as constants keeps them in one place,
8
+ # makes typos a load-time error instead of a silent always-off flag, and lets
9
+ # tools find every reference to a flag.
10
+ #
11
+ # `FeatureFlag` is a constant, so it is never nil and safe navigation
12
+ # (`FeatureFlag&.active?`) is never used; the cop only handles `on_send`.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # FeatureFlag.active?("some_feature_flag")
17
+ #
18
+ # # good
19
+ # FeatureFlag.active?(SomeModule::SOME_FEATURE_FLAG)
20
+ class FeatureFlagConstants < Base
21
+ MSG = "FeatureFlag keys should be constants, not strings"
22
+ RESTRICT_ON_SEND = %i(active?).freeze
23
+
24
+ # @!method feature_flag_with_string?(node)
25
+ def_node_matcher :feature_flag_with_string?, <<~PATTERN
26
+ (send (const nil? :FeatureFlag) :active? (str _) ...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ add_offense(node) if feature_flag_with_string?(node)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -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.3.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.3.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
@@ -132,20 +160,22 @@ files:
132
160
  - lib/rubocop/cop/gusto/discouraged_gem.rb
133
161
  - lib/rubocop/cop/gusto/execute_migration.rb
134
162
  - lib/rubocop/cop/gusto/factory_classes_or_modules.rb
163
+ - lib/rubocop/cop/gusto/feature_flag_constants.rb
135
164
  - lib/rubocop/cop/gusto/min_by_max_by.rb
136
165
  - lib/rubocop/cop/gusto/no_metaprogramming.rb
137
166
  - lib/rubocop/cop/gusto/no_rescue_error_message_checking.rb
138
167
  - lib/rubocop/cop/gusto/no_send.rb
139
168
  - lib/rubocop/cop/gusto/paperclip_or_attachable.rb
140
169
  - lib/rubocop/cop/gusto/perform_class_method.rb
170
+ - lib/rubocop/cop/gusto/pluck_on_select.rb
141
171
  - lib/rubocop/cop/gusto/polymorphic_type_validation.rb
142
172
  - lib/rubocop/cop/gusto/prefer_process_last_status.rb
143
173
  - lib/rubocop/cop/gusto/rabl_extends.rb
144
- - lib/rubocop/cop/gusto/rails_env.rb
145
174
  - lib/rubocop/cop/gusto/rake_constants.rb
146
175
  - lib/rubocop/cop/gusto/regexp_bypass.rb
147
176
  - lib/rubocop/cop/gusto/rspec_date_time_mock.rb
148
177
  - lib/rubocop/cop/gusto/sidekiq_params.rb
178
+ - lib/rubocop/cop/gusto/smart_todo_team.rb
149
179
  - lib/rubocop/cop/gusto/toplevel_constants.rb
150
180
  - lib/rubocop/cop/gusto/unreferenced_let.rb
151
181
  - 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