betterlint 1.28.0 → 1.29.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: 070e58e2493975205fb3f5f52f1c6082109835db4388e67d7876bfd471861e59
4
- data.tar.gz: 73a4c6ddd11054bd6b2c3add1e3963f62b6849241b2eb4be2a41a1bec41cc2f7
3
+ metadata.gz: 4be70714f121b7f33abf6c01f95ff109af80785e27d242225d3e4b0dcf3baa2a
4
+ data.tar.gz: 233a10f1796cd42ebdebfa2ef715d7252f9d30abc6498e4f5cb5cec57b3f28a9
5
5
  SHA512:
6
- metadata.gz: c212dd812b46431b40bea0876f5aeefe5d4f657ef59bb0355342a4e22d19e167cbe950ca7edd237ad581e17cc2400f7c08cabc84ce677b16731f56957ba517d6
7
- data.tar.gz: 486bdb3b54a34ee2363cf482e113a928840bda36ca94a9407e2b269748fa534a24d75e407df839d5b86561dedb50c9a1648558b507ae49acf6152b77866cfb8a
6
+ metadata.gz: 7eebfe8e66e6098b8f78a2c5e564b51500bbbb5391f7fb341558495b0a8a8e3124f11be94a0d25ea7a3648a56c7d945d8f39815bdc30d28b8aff5374a7b768f2
7
+ data.tar.gz: d3f7249277cc5268cd7b6182fd4a21f98cf9e8150c6544a10af4476b879bded4675a9851b8b7f9faf541cc1863de573e6c4d6ef4969e8382338fd78620b3a08b
data/README.md CHANGED
@@ -367,3 +367,59 @@ the `coder` keyword argument.
367
367
  - And you've opted into 7.1 defaults (namely, `config.active_record.default_column_serializer = nil`)
368
368
 
369
369
  ...you can safely disable this cop, since failing to pass a deserializer will raise an exception.
370
+
371
+ ### Betterment/SpecDescribeMethodName
372
+
373
+ This cop requires each spec to name the class under test and the method under test using `describe` blocks. The outermost `describe` blocks should be the class constant, and second level `describe` blocks should be of the form `"#instance_method"` or `".class_method"`. This structure works with [mutant](https://github.com/mbj/mutant) and creates consistently shaped specs
374
+
375
+ ```ruby
376
+ # BAD - the outer describe is a string
377
+ RSpec.describe "Invoice" do
378
+ # ...
379
+ end
380
+
381
+ # BAD - no method label
382
+ RSpec.describe Invoice do
383
+ it "sums the line items" do
384
+ # ...
385
+ end
386
+ end
387
+
388
+ # BAD - the context block breaks the "Invoice#total" label
389
+ RSpec.describe Invoice do
390
+ context "when the account is delinquent" do
391
+ describe "#total" do
392
+ # ...
393
+ end
394
+ end
395
+ end
396
+
397
+ # BAD - a second class describe breaks the "Invoice::Item#total" label.
398
+ # Give Invoice::Item its own spec file.
399
+ RSpec.describe Invoice do
400
+ describe Invoice::Item do
401
+ describe "#total" do
402
+ # ...
403
+ end
404
+ end
405
+ end
406
+
407
+ # GOOD - shared setup above the method labels
408
+ RSpec.describe Invoice do
409
+ let(:account) { create(:account, :delinquent) }
410
+
411
+ describe "#total" do
412
+ it "adds the late fee" do
413
+ # ...
414
+ end
415
+ end
416
+
417
+ describe ".open" do
418
+ it "excludes paid invoices" do
419
+ # ...
420
+ end
421
+ end
422
+ end
423
+ ```
424
+
425
+ This cop is disabled by default, because it reports many offenses in applications with older specs. To use it, enable it in your `.rubocop.yml`. By default it examines all files in `spec/`, except for the `features`, `requests`, `routing`, `system`, and `views` directories, where integration style tests that don't test one class and method are common. Examples in shared example groups are also ignored.
data/config/default.yml CHANGED
@@ -114,6 +114,19 @@ Betterment/SitePrismLoaded:
114
114
  - spec/features/**/*_spec.rb
115
115
  - spec/system/**/*_spec.rb
116
116
 
117
+ Betterment/SpecDescribeMethodName:
118
+ Description: Requires spec `describe` blocks to be labeled with a class constant and method names.
119
+ Enabled: false
120
+ StyleGuide: '#bettermentspecdescribemethodname'
121
+ Include:
122
+ - spec/**/*_spec.rb
123
+ Exclude:
124
+ - spec/features/**/*_spec.rb
125
+ - spec/requests/**/*_spec.rb
126
+ - spec/routing/**/*_spec.rb
127
+ - spec/system/**/*_spec.rb
128
+ - spec/views/**/*_spec.rb
129
+
117
130
  Betterment/UnsafeJob:
118
131
  Enabled: false
119
132
  StyleGuide: '#bettermentunsafejob'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Betterlint
4
- VERSION = '1.28.0'
4
+ VERSION = '1.29.0'
5
5
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Betterment
6
+ class SpecDescribeMethodName < Base
7
+ MSG_CLASS_LABEL = 'Label the outer `describe` with the class constant, not a string.'
8
+ MSG_METHOD_LABEL = 'Put examples inside a `describe` labeled with a method name, such as `"#instance_method"` or `".class_method"`.'
9
+ MSG_DIRECTLY_INSIDE = 'Put the method `describe` directly inside the class `describe`, and share setup with `let` or `before`.'
10
+ MSG_ONE_CLASS = 'Describe one class in each spec file, and put the method `describe` directly inside the outer `describe`.'
11
+
12
+ METHOD_LABEL = /\A[#.]\S/
13
+
14
+ DESCRIBE_METHODS = %i(describe fdescribe xdescribe).freeze
15
+ EXAMPLE_GROUP_METHODS = %i(describe fdescribe xdescribe context fcontext xcontext).freeze
16
+ SHARED_GROUP_METHODS = %i(shared_examples shared_examples_for shared_context).freeze
17
+ EXAMPLE_METHODS = %i(
18
+ it its specify example scenario
19
+ fit fspecify fexample fscenario
20
+ xit xspecify xexample xscenario
21
+ ).freeze
22
+
23
+ RESTRICT_ON_SEND = (DESCRIBE_METHODS + EXAMPLE_METHODS).freeze
24
+
25
+ def on_send(node)
26
+ if DESCRIBE_METHODS.include?(node.method_name)
27
+ check_describe(node) if rspec_receiver?(node)
28
+ elsif node.receiver.nil?
29
+ check_example(node)
30
+ end
31
+ end
32
+ alias on_csend on_send
33
+
34
+ private
35
+
36
+ def check_describe(node)
37
+ groups = enclosing_groups(node)
38
+ return if groups.any? { |group| shared_group?(group) }
39
+
40
+ check_class_label(node) if groups.empty?
41
+ check_method_describe_placement(node, groups)
42
+ end
43
+
44
+ def check_example(node)
45
+ groups = enclosing_groups(node)
46
+ return if groups.any? { |group| shared_group?(group) || method_describe?(group) }
47
+ return unless groups.any? { |group| class_describe?(group) }
48
+
49
+ add_offense(node, message: MSG_METHOD_LABEL)
50
+ end
51
+
52
+ def check_class_label(send_node)
53
+ label = send_node.first_argument
54
+ return if label.nil? || label.const_type?
55
+
56
+ add_offense(label, message: MSG_CLASS_LABEL)
57
+ end
58
+
59
+ def check_method_describe_placement(send_node, groups)
60
+ return unless method_describe?(send_node)
61
+
62
+ parent = groups.first
63
+ return if parent.nil? || (groups.one? && class_describe?(parent))
64
+ return unless groups.any? { |group| class_describe?(group) }
65
+
66
+ add_offense(send_node, message: class_describe?(parent) ? MSG_ONE_CLASS : MSG_DIRECTLY_INSIDE)
67
+ end
68
+
69
+ def enclosing_groups(node)
70
+ node.each_ancestor(:block, :numblock).filter_map do |ancestor|
71
+ send_node = ancestor.send_node
72
+ next if send_node.equal?(node)
73
+
74
+ send_node if example_group?(send_node) || shared_group?(send_node)
75
+ end
76
+ end
77
+
78
+ def example_group?(send_node)
79
+ EXAMPLE_GROUP_METHODS.include?(send_node.method_name) && rspec_receiver?(send_node)
80
+ end
81
+
82
+ def shared_group?(send_node)
83
+ SHARED_GROUP_METHODS.include?(send_node.method_name) && rspec_receiver?(send_node)
84
+ end
85
+
86
+ def describe?(send_node)
87
+ DESCRIBE_METHODS.include?(send_node.method_name) && rspec_receiver?(send_node)
88
+ end
89
+
90
+ def class_describe?(send_node)
91
+ return false unless describe?(send_node)
92
+
93
+ send_node.first_argument&.const_type? || false
94
+ end
95
+
96
+ def method_describe?(send_node)
97
+ return false unless describe?(send_node)
98
+
99
+ label = send_node.first_argument
100
+ return false unless label&.str_type?
101
+
102
+ METHOD_LABEL.match?(label.value)
103
+ end
104
+
105
+ def rspec_receiver?(send_node)
106
+ receiver = send_node.receiver
107
+ return true if receiver.nil?
108
+
109
+ receiver.const_type? && receiver.short_name == :RSpec && (receiver.namespace.nil? || receiver.namespace.cbase_type?)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -21,6 +21,7 @@ require 'rubocop/cop/betterment/render_status'
21
21
  require 'rubocop/cop/betterment/server_error_assertion'
22
22
  require 'rubocop/cop/betterment/simple_delegator'
23
23
  require 'rubocop/cop/betterment/site_prism_loaded'
24
+ require 'rubocop/cop/betterment/spec_describe_method_name'
24
25
  require 'rubocop/cop/betterment/spec_helper_required_outside_spec_dir'
25
26
  require 'rubocop/cop/betterment/timeout'
26
27
  require 'rubocop/cop/betterment/unsafe_job'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: betterlint
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.28.0
4
+ version: 1.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Development
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-01-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: lint_roller
@@ -189,6 +188,7 @@ files:
189
188
  - lib/rubocop/cop/betterment/server_error_assertion.rb
190
189
  - lib/rubocop/cop/betterment/simple_delegator.rb
191
190
  - lib/rubocop/cop/betterment/site_prism_loaded.rb
191
+ - lib/rubocop/cop/betterment/spec_describe_method_name.rb
192
192
  - lib/rubocop/cop/betterment/spec_helper_required_outside_spec_dir.rb
193
193
  - lib/rubocop/cop/betterment/timeout.rb
194
194
  - lib/rubocop/cop/betterment/unsafe_job.rb
@@ -203,13 +203,12 @@ licenses:
203
203
  - MIT
204
204
  metadata:
205
205
  homepage_uri: https://github.com/Betterment/betterlint
206
- source_code_uri: https://github.com/Betterment/betterlint/tree/v1.28.0
207
- changelog_uri: https://github.com/Betterment/betterlint/blob/v1.28.0/CHANGELOG.md
206
+ source_code_uri: https://github.com/Betterment/betterlint/tree/v1.29.0
207
+ changelog_uri: https://github.com/Betterment/betterlint/blob/v1.29.0/CHANGELOG.md
208
208
  bug_tracker_uri: https://github.com/Betterment/betterlint/issues
209
- documentation_uri: https://www.rubydoc.info/gems/betterlint/1.28.0
209
+ documentation_uri: https://www.rubydoc.info/gems/betterlint/1.29.0
210
210
  rubygems_mfa_required: 'true'
211
211
  default_lint_roller_plugin: Betterlint::Plugin
212
- post_install_message:
213
212
  rdoc_options: []
214
213
  require_paths:
215
214
  - lib
@@ -224,8 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
223
  - !ruby/object:Gem::Version
225
224
  version: '0'
226
225
  requirements: []
227
- rubygems_version: 3.4.10
228
- signing_key:
226
+ rubygems_version: 4.0.13
229
227
  specification_version: 4
230
228
  summary: Betterment rubocop configuration
231
229
  test_files: []