betterlint 1.23.0 → 1.25.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: e78f83970d4e6b36b46992475351b250c46011a6038cb48eab065ab96dc602b7
4
- data.tar.gz: 8a8638f233932cab03d50d11d34c90a1abc043823be86c75252f2a79ff2773bb
3
+ metadata.gz: 04c560ef56ed92b4393449c238d64d433b16caa1db01dbcb81c647c04f9931d6
4
+ data.tar.gz: bac5e05a6ec457e6c047eba8c1d709549167ceae3b408fa084c5aa83c9594676
5
5
  SHA512:
6
- metadata.gz: 6a95aa48d9f871dd4c1b09f3361d62808d058afe8739a8d5ea85c2ed28aedb43e16c2cc6c54e9b8bbff27d31dd03215abd0c0701df90abaa8f7e77d0caf984cf
7
- data.tar.gz: dd86cb3a940ced9cde0eb1902aa435bfe66f7bb7c94b2cd08d3f340f1c6f86537ec4eee890dcaeb4f15c62f2524029cc576c6b5a03df5c57f26f5de401b8b0b6
6
+ metadata.gz: 57bf6d2446f5ec19e463e3bad15bb22934b08104a7614f08b1f67291bd8243ef7c1fdbccd7d8259f8b7b69c10f995248561c2e2cecf032c2bac01df2fd074aba
7
+ data.tar.gz: f2cee92d7b16ba483562f7bb1ebf822ff186e106cbf1df9f3f026f013f801a2f4f9fb01737e50b3e8bbfe83f4ee26af1b44614e35a48cf65028e7d3f6afb1dd2
data/README.md CHANGED
@@ -142,13 +142,18 @@ If there isn't a better place to assign your environment variable, Rails provide
142
142
  for [custom configuration](https://guides.rubyonrails.org/configuring.html#custom-configuration):
143
143
 
144
144
  ```ruby
145
- config.x.whatever = ENV.fetch('WHATEVER')
145
+ config.x.some_namespace.whatever = ENV.fetch('WHATEVER')
146
146
  ```
147
147
 
148
148
  Here's how you'd reference this configuration parameter at runtime:
149
149
 
150
150
  ```ruby
151
- Rails.configuration.x.whatever
151
+ Rails.configuration.x.some_namespace.whatever
152
+ ```
153
+
154
+ Or to raise an error when the value is not present
155
+ ```ruby
156
+ Rails.configuration.x.some_namespace.whatever! # will raise "KeyError: :whatever is blank" when value is not set or set to nil
152
157
  ```
153
158
 
154
159
  ### Betterment/InternalsProtection
@@ -302,6 +307,39 @@ This cop requires you to explicitly provide an HTTP status code when rendering a
302
307
  create, update, and destroy actions. When autocorrecting, this will automatically add
303
308
  `status: :unprocessable_entity` or `status: :ok` depending on what you're rendering.
304
309
 
310
+ ### Betterment/SimpleDelegator
311
+
312
+ This cop requires you to use Rails's `delegate` class method instead of `SimpleDelegator` in order to explicitly specify
313
+ the set of delegated methods.
314
+
315
+ #### BAD:
316
+
317
+ ```ruby
318
+ class GearPresenter < SimpleDelegator
319
+ def ratio_string
320
+ ratio.to_s
321
+ end
322
+ end
323
+ ```
324
+
325
+ #### GOOD:
326
+
327
+ ```ruby
328
+ class GearPresenter
329
+ attr_reader :gear
330
+
331
+ delegate :ratio, to: :gear
332
+
333
+ def initialize(gear)
334
+ @gear = gear
335
+ end
336
+
337
+ def ratio_string
338
+ ratio.to_s
339
+ end
340
+ end
341
+ ```
342
+
305
343
  ### Betterment/UseGlobalStrictLoading/ByDefaultForModels
306
344
 
307
345
  This cop identifies models where `self.strict_loading_by_default` is assigned to explicitly, and prefers that it be removed in favor of using the global strict loading settings.
data/STYLEGUIDE.md CHANGED
@@ -109,3 +109,36 @@ expect(response).to have_http_status 422
109
109
  expect(response).to have_http_status :internal_server_error
110
110
  expect(response).to have_http_status 500
111
111
  ```
112
+
113
+ ## Betterment/SimpleDelegator
114
+
115
+ This cop requires you to use Rail's `delegate` class method instead of `SimpleDelegator` in order to explicitly specify
116
+ the set of delegating methods.
117
+
118
+ ### BAD:
119
+
120
+ ```ruby
121
+ class GearPresenter < SimpleDelegator
122
+ def ratio_string
123
+ ratio.to_s
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### GOOD:
129
+
130
+ ```ruby
131
+ class GearDelegator
132
+ attr_reader :gear
133
+
134
+ delegate :ratio, to: :gear
135
+
136
+ def initialize(gear)
137
+ @gear = gear
138
+ end
139
+
140
+ def ratio_string
141
+ ratio.to_s
142
+ end
143
+ end
144
+ ```
data/config/default.yml CHANGED
@@ -95,6 +95,10 @@ Betterment/ServerErrorAssertion:
95
95
  Include:
96
96
  - spec/requests/**/*_spec.rb
97
97
 
98
+ Betterment/SimpleDelegator:
99
+ StyleGuide: '#bettermentsimpledelegator'
100
+ AutoCorrect: false
101
+
98
102
  Betterment/SitePrismLoaded:
99
103
  Include:
100
104
  - spec/features/**/*_spec.rb
@@ -37,17 +37,17 @@ module RuboCop
37
37
  super
38
38
  @unsafe_parameters = cop_config.fetch("unsafe_parameters").map(&:to_sym)
39
39
  @unsafe_regex = Regexp.new cop_config.fetch("unsafe_regex")
40
- @param_wrappers = []
40
+ end
41
+
42
+ def on_new_investigation
43
+ super
44
+ @class_methods = {}.freeze
45
+ @param_wrappers = [].freeze
41
46
  end
42
47
 
43
48
  def on_class(node)
44
- Utils::MethodReturnTable.populate_index node
45
- Utils::MethodReturnTable.indexed_methods.each do |method_name, method_returns|
46
- method_returns.each do |x|
47
- name = Utils::Parser.get_root_token(x)
48
- @param_wrappers << method_name if name == :params || @param_wrappers.include?(name)
49
- end
50
- end
49
+ @class_methods = Utils::Parser.get_instance_methods(node).freeze
50
+ @param_wrappers = find_param_wrappers(@class_methods).freeze
51
51
  end
52
52
 
53
53
  def on_send(node) # rubocop:disable Metrics/PerceivedComplexity
@@ -88,7 +88,7 @@ module RuboCop
88
88
 
89
89
  # Flags objects being created/updated with unsafe
90
90
  # params indirectly from params or through params.permit
91
- def flag_indirect_param_use(node) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
91
+ def flag_indirect_param_use(node) # rubocop:disable Metrics/PerceivedComplexity
92
92
  name = Utils::Parser.get_root_token(node)
93
93
  # extracted_params contains parameters used like:
94
94
  # def create
@@ -99,7 +99,7 @@ module RuboCop
99
99
  # end
100
100
  extracted_params = Utils::Parser.get_extracted_parameters(node, param_aliases: @param_wrappers)
101
101
 
102
- returns = Utils::MethodReturnTable.get_method(name) || []
102
+ returns = get_method_returns(name)
103
103
  returns.each do |ret|
104
104
  # # propagated_params contains parameters used like:
105
105
  # def create
@@ -120,7 +120,7 @@ module RuboCop
120
120
  if ret.send_type? && ret.method?(:[])
121
121
  internal_params = ret.arguments.select { |x| x.sym_type? || x.str_type? }.map(&:value)
122
122
  else
123
- internal_returns = Utils::MethodReturnTable.get_method(Utils::Parser.get_root_token(ret)) || []
123
+ internal_returns = get_method_returns(Utils::Parser.get_root_token(ret))
124
124
  internal_params = internal_returns.flat_map { |x| Utils::Parser.get_extracted_parameters(x, param_aliases: @param_wrappers) }
125
125
  end
126
126
 
@@ -144,6 +144,19 @@ module RuboCop
144
144
  def suspicious_id?(symbol_name)
145
145
  @unsafe_parameters.include?(symbol_name.to_sym) || @unsafe_regex.match(symbol_name) # symbol_name.to_s.end_with?("_id")
146
146
  end
147
+
148
+ def find_param_wrappers(class_methods)
149
+ class_methods.each_with_object([]) do |(method_name, method_returns), param_wrappers|
150
+ param_wrappers << method_name if method_returns.any? do |return_value|
151
+ name = Utils::Parser.get_root_token(return_value)
152
+ name.equal?(:params) || param_wrappers.include?(name)
153
+ end
154
+ end
155
+ end
156
+
157
+ def get_method_returns(method_name)
158
+ @class_methods.fetch(method_name, [])
159
+ end
147
160
  end
148
161
  end
149
162
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Betterment
6
+ class SimpleDelegator < Base
7
+ MSG = <<~MSG
8
+ In order to specify a set of explicitly available methods,
9
+ use the `delegate` class method instead of `SimpleDelegator`.
10
+
11
+ See here for more information on this error:
12
+ https://github.com/Betterment/betterlint/#bettermentsimpledelegator
13
+ MSG
14
+
15
+ # @!method class_with_simple_delegator?(node)
16
+ def_node_matcher :class_with_simple_delegator?, <<~PATTERN
17
+ (class _ (const nil? :SimpleDelegator) _)
18
+ PATTERN
19
+
20
+ def on_class(node)
21
+ add_offense(node) if class_with_simple_delegator?(node)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -40,8 +40,13 @@ module RuboCop
40
40
  @unauthenticated_models = cop_config.fetch("unauthenticated_models").map(&:to_sym)
41
41
  end
42
42
 
43
+ def on_new_investigation
44
+ super
45
+ @class_methods = {}.freeze
46
+ end
47
+
43
48
  def on_class(node)
44
- Utils::MethodReturnTable.populate_index(node)
49
+ @class_methods = Utils::Parser.get_instance_methods(node).freeze
45
50
  end
46
51
 
47
52
  def on_send(node)
@@ -86,9 +91,7 @@ module RuboCop
86
91
 
87
92
  def uses_params?(node)
88
93
  root = Utils::Parser.get_root_token(node)
89
- root == :params || Array(Utils::MethodReturnTable.get_method(root)).find do |x|
90
- Utils::Parser.get_root_token(x) == :params
91
- end
94
+ root.equal?(:params) || method_returns_params?(root)
92
95
  end
93
96
 
94
97
  # yoinked from Rails/DynamicFindBy
@@ -98,6 +101,12 @@ module RuboCop
98
101
 
99
102
  match[2] ? 'find_by!' : 'find_by'
100
103
  end
104
+
105
+ def method_returns_params?(method_name)
106
+ @class_methods.fetch(method_name, []).any? do |return_value|
107
+ Utils::Parser.get_root_token(return_value).equal?(:params)
108
+ end
109
+ end
101
110
  end
102
111
  end
103
112
  end
@@ -88,16 +88,16 @@ module RuboCop
88
88
  return [] unless node.send_type?
89
89
 
90
90
  parameter_names = []
91
- param_aliases << :params
91
+ aliases = param_aliases | %i(params)
92
92
 
93
- if node.method?(:[]) && param_aliases.include?(get_root_token(node))
93
+ if node.method?(:[]) && aliases.include?(get_root_token(node))
94
94
  return node.arguments.select { |x|
95
95
  x.sym_type? || x.str_type?
96
96
  }.map(&:value)
97
97
  end
98
98
 
99
99
  children = node.descendants.select do |child|
100
- child.send_type? && param_aliases.include?(child.method_name)
100
+ child.send_type? && aliases.include?(child.method_name)
101
101
  end
102
102
 
103
103
  children.each do |child|
@@ -112,6 +112,23 @@ module RuboCop
112
112
 
113
113
  parameter_names.map(&:to_sym)
114
114
  end
115
+
116
+ def self.get_instance_methods(node) # rubocop:disable Metrics/PerceivedComplexity
117
+ raise ArgumentError, 'must be a class node' unless node&.class_type?
118
+
119
+ methods = node.descendants.select(&:def_type?).to_h do |method|
120
+ [method.method_name, get_return_values(method)]
121
+ end
122
+
123
+ node.descendants.each do |descendant|
124
+ lhs, rhs = *descendant
125
+ if descendant.equals_asgn? && !descendant.type.equal?(:casgn) && rhs&.send_type?
126
+ methods[lhs] = [rhs]
127
+ end
128
+ end
129
+
130
+ methods
131
+ end
115
132
  end
116
133
  end
117
134
  end
@@ -18,6 +18,7 @@ require 'rubocop/cop/betterment/not_using_rswag'
18
18
  require 'rubocop/cop/betterment/redirect_status'
19
19
  require 'rubocop/cop/betterment/render_status'
20
20
  require 'rubocop/cop/betterment/server_error_assertion'
21
+ require 'rubocop/cop/betterment/simple_delegator'
21
22
  require 'rubocop/cop/betterment/site_prism_loaded'
22
23
  require 'rubocop/cop/betterment/spec_helper_required_outside_spec_dir'
23
24
  require 'rubocop/cop/betterment/timeout'
@@ -25,6 +26,5 @@ require 'rubocop/cop/betterment/unsafe_job'
25
26
  require 'rubocop/cop/betterment/unscoped_find'
26
27
  require 'rubocop/cop/betterment/use_global_strict_loading'
27
28
  require 'rubocop/cop/betterment/utils/hardcoded_attribute'
28
- require 'rubocop/cop/betterment/utils/method_return_table'
29
29
  require 'rubocop/cop/betterment/utils/parser'
30
30
  require 'rubocop/cop/betterment/vague_serialize'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: betterlint
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.0
4
+ version: 1.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Development
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rubocop
@@ -121,6 +121,7 @@ files:
121
121
  - lib/rubocop/cop/betterment/redirect_status.rb
122
122
  - lib/rubocop/cop/betterment/render_status.rb
123
123
  - lib/rubocop/cop/betterment/server_error_assertion.rb
124
+ - lib/rubocop/cop/betterment/simple_delegator.rb
124
125
  - lib/rubocop/cop/betterment/site_prism_loaded.rb
125
126
  - lib/rubocop/cop/betterment/spec_helper_required_outside_spec_dir.rb
126
127
  - lib/rubocop/cop/betterment/timeout.rb
@@ -128,7 +129,6 @@ files:
128
129
  - lib/rubocop/cop/betterment/unscoped_find.rb
129
130
  - lib/rubocop/cop/betterment/use_global_strict_loading.rb
130
131
  - lib/rubocop/cop/betterment/utils/hardcoded_attribute.rb
131
- - lib/rubocop/cop/betterment/utils/method_return_table.rb
132
132
  - lib/rubocop/cop/betterment/utils/parser.rb
133
133
  - lib/rubocop/cop/betterment/utils/response_status.rb
134
134
  - lib/rubocop/cop/betterment/vague_serialize.rb
@@ -137,10 +137,10 @@ licenses:
137
137
  - MIT
138
138
  metadata:
139
139
  homepage_uri: https://github.com/Betterment/betterlint
140
- source_code_uri: https://github.com/Betterment/betterlint/tree/v1.23.0
141
- changelog_uri: https://github.com/Betterment/betterlint/blob/v1.23.0/CHANGELOG.md
140
+ source_code_uri: https://github.com/Betterment/betterlint/tree/v1.25.0
141
+ changelog_uri: https://github.com/Betterment/betterlint/blob/v1.25.0/CHANGELOG.md
142
142
  bug_tracker_uri: https://github.com/Betterment/betterlint/issues
143
- documentation_uri: https://www.rubydoc.info/gems/betterlint/1.23.0
143
+ documentation_uri: https://www.rubydoc.info/gems/betterlint/1.25.0
144
144
  rubygems_mfa_required: 'true'
145
145
  rdoc_options: []
146
146
  require_paths:
@@ -156,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
156
  - !ruby/object:Gem::Version
157
157
  version: '0'
158
158
  requirements: []
159
- rubygems_version: 3.6.6
159
+ rubygems_version: 3.6.8
160
160
  specification_version: 4
161
161
  summary: Betterment rubocop configuration
162
162
  test_files: []
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Betterment
6
- module Utils
7
- module MethodReturnTable
8
- class << self
9
- def populate_index(node)
10
- raise "not a class" unless node.class_type?
11
-
12
- get_methods_for_class(node).each do |method|
13
- track_method(method.method_name, Utils::Parser.get_return_values(method))
14
- end
15
-
16
- node.descendants.each do |descendant|
17
- lhs, rhs = *descendant
18
- next unless descendant.equals_asgn? && (descendant.type != :casgn) && rhs&.send_type?
19
-
20
- track_method(lhs, [rhs])
21
- end
22
- end
23
-
24
- def indexed_methods
25
- @indexed_methods ||= {}
26
- end
27
-
28
- def get_method(method_name)
29
- indexed_methods[method_name]
30
- end
31
-
32
- def has_method?(method_name)
33
- indexed_methods.include?(method_name)
34
- end
35
-
36
- private
37
-
38
- def track_method(method_name, returns)
39
- indexed_methods[method_name] = returns
40
- end
41
-
42
- def get_methods_for_class(node)
43
- return [] unless node.children && node.class_type?
44
-
45
- node.descendants.select(&:def_type?)
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end
52
- end