rubocop-rspec 2.23.2 → 2.24.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: 2312779cc594656bb1473fc821f67c6a56ac6275c71ac536c5978a4ef4411384
4
- data.tar.gz: 0a6419dba263c16228b454d84e25a6b066de368ebd96a98c0e2e9f66ff74f053
3
+ metadata.gz: 7cd039667fb1c9cab8a794b4d4d028a1acdef5cc9033aa37fd37b8ddc6fa00c0
4
+ data.tar.gz: c80d4491b4e9d124346195d38f73a3b13d251a4259fa459783d6dbb471ed9925
5
5
  SHA512:
6
- metadata.gz: 1ed0d4251d930a51e2214b200f5720fa7f75b839e40164a6461992ee01e22518e435d12f2cd3c626f0af24c47b435bfce4f80765eadc1540e848c993a97cfae9
7
- data.tar.gz: a74d40480225c7b693f33ff4237a19eb44188f6b634ee60cbec96cf89d03b356b61fa5130c2939a17a23331390edbe0b9f7ed4f6a2e08606bbe88d69b9631bf0
6
+ metadata.gz: eccda68a017df50004a38d6e55c6fbabeef13fa20aa37941aec18042adcace438c5cdd6ef821daed30cd17cb2884af79bf2a882f7da875e7190ed437c2e061b9
7
+ data.tar.gz: '0832c87dd57fba0540c7d624b6bf5e38046b8a9bdc7ee82ee1c1a065a7d4bd6ba3f7949da6a9592d01de3ad8406bd8cc8433d309717cd5993314eaeecac076bb'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 2.24.0 (2023-09-08)
6
+
7
+ - Split `RSpec/FilePath` into `RSpec/SpecFilePathSuffix` and `RSpec/SpecFilePathFormat`. `RSpec/FilePath` cop is enabled by default, the two new cops are pending and need to be enabled explicitly. ([@ydah])
8
+ - Add new `RSpec/Eq` cop. ([@ydah])
9
+ - Add `RSpec/MetadataStyle` and `RSpec/EmptyMetadata` cops. ([@r7kamura])
10
+ - Add support `RSpec/Rails/HttpStatus` when `have_http_status` with string argument. ([@ydah])
11
+ - Fix an infinite loop error when `RSpec/ExcessiveDocstringSpacing` finds a description with non-ASCII leading/trailing whitespace. ([@bcgraham])
12
+ - Fix an incorrect autocorrect for `RSpec/ReceiveMessages` when return values declared between stubs. ([@marocchino])
13
+ - Fix a false positive `RSpec/Focus` when chained method call and inside define method. ([@ydah])
14
+
5
15
  ## 2.23.2 (2023-08-09)
6
16
 
7
17
  - Fix an incorrect autocorrect for `RSpec/ReceiveMessages` when method is only non-word character. ([@marocchino])
@@ -797,6 +807,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
797
807
  [@aried3r]: https://github.com/aried3r
798
808
  [@baberthal]: https://github.com/baberthal
799
809
  [@backus]: https://github.com/backus
810
+ [@bcgraham]: https://github.com/bcgraham
800
811
  [@biinari]: https://github.com/biinari
801
812
  [@bmorrall]: https://github.com/bmorrall
802
813
  [@bquorning]: https://github.com/bquorning
data/config/default.yml CHANGED
@@ -359,6 +359,18 @@ RSpec/EmptyLineAfterSubject:
359
359
  StyleGuide: https://rspec.rubystyle.guide/#empty-line-after-let
360
360
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterSubject
361
361
 
362
+ RSpec/EmptyMetadata:
363
+ Description: Avoid empty metadata hash.
364
+ Enabled: pending
365
+ VersionAdded: '2.24'
366
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata
367
+
368
+ RSpec/Eq:
369
+ Description: Use `eq` instead of `be ==` to compare objects.
370
+ Enabled: pending
371
+ VersionAdded: '2.24'
372
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Eq
373
+
362
374
  RSpec/ExampleLength:
363
375
  Description: Checks for long examples.
364
376
  Enabled: true
@@ -436,7 +448,7 @@ RSpec/ExpectOutput:
436
448
 
437
449
  RSpec/FilePath:
438
450
  Description: Checks that spec file paths are consistent and well-formed.
439
- Enabled: true
451
+ Enabled: false
440
452
  Include:
441
453
  - "**/*_spec*rb*"
442
454
  - "**/spec/**/*"
@@ -446,7 +458,7 @@ RSpec/FilePath:
446
458
  IgnoreMethods: false
447
459
  SpecSuffixOnly: false
448
460
  VersionAdded: '1.2'
449
- VersionChanged: '1.40'
461
+ VersionChanged: '2.24'
450
462
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FilePath
451
463
 
452
464
  RSpec/Focus:
@@ -613,6 +625,16 @@ RSpec/MessageSpies:
613
625
  VersionAdded: '1.9'
614
626
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageSpies
615
627
 
628
+ RSpec/MetadataStyle:
629
+ Description: Use consistent metadata style.
630
+ Enabled: pending
631
+ EnforcedStyle: symbol
632
+ SupportedStyles:
633
+ - hash
634
+ - symbol
635
+ VersionAdded: '2.24'
636
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MetadataStyle
637
+
616
638
  RSpec/MissingExampleGroupArgument:
617
639
  Description: Checks that the first argument to an example group is not empty.
618
640
  Enabled: true
@@ -834,6 +856,31 @@ RSpec/SortMetadata:
834
856
  VersionAdded: '2.14'
835
857
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SortMetadata
836
858
 
859
+ RSpec/SpecFilePathFormat:
860
+ Description: Checks that spec file paths are consistent and well-formed.
861
+ Enabled: pending
862
+ Include:
863
+ - "**/*_spec.rb"
864
+ Exclude:
865
+ - "**/spec/routing/**/*"
866
+ CustomTransform:
867
+ RuboCop: rubocop
868
+ RSpec: rspec
869
+ IgnoreMethods: false
870
+ IgnoreMetadata:
871
+ type: routing
872
+ VersionAdded: '2.24'
873
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathFormat
874
+
875
+ RSpec/SpecFilePathSuffix:
876
+ Description: Checks that spec file paths suffix are consistent and well-formed.
877
+ Enabled: pending
878
+ VersionAdded: '2.24'
879
+ Include:
880
+ - "**/*_spec*rb*"
881
+ - "**/spec/**/*"
882
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathSuffix
883
+
837
884
  RSpec/StubbedMock:
838
885
  Description: Checks that message expectations do not have a configured response.
839
886
  Enabled: true
@@ -27,3 +27,9 @@ renamed:
27
27
  RSpec/FactoryBot/FactoryClassName: FactoryBot/FactoryClassName
28
28
  RSpec/FactoryBot/FactoryNameStyle: FactoryBot/FactoryNameStyle
29
29
  RSpec/FactoryBot/SyntaxMethods: FactoryBot/SyntaxMethods
30
+
31
+ split:
32
+ RSpec/FilePath:
33
+ alternatives:
34
+ - RSpec/SpecFilePathFormat
35
+ - RSpec/SpecFilePathSuffix
@@ -19,7 +19,7 @@ module RuboCop
19
19
 
20
20
  MSG = 'Avoid duplicated metadata.'
21
21
 
22
- def on_metadata(symbols, _pairs)
22
+ def on_metadata(symbols, _hash)
23
23
  symbols.each do |symbol|
24
24
  on_metadata_symbol(symbol)
25
25
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Avoid empty metadata hash.
7
+ #
8
+ # @example EnforcedStyle: symbol (default)
9
+ # # bad
10
+ # describe 'Something', {}
11
+ #
12
+ # # good
13
+ # describe 'Something'
14
+ class EmptyMetadata < Base
15
+ extend AutoCorrector
16
+
17
+ include Metadata
18
+ include RangeHelp
19
+
20
+ MSG = 'Avoid empty metadata hash.'
21
+
22
+ def on_metadata(_symbols, hash)
23
+ return unless hash&.pairs&.empty?
24
+
25
+ add_offense(hash) do |corrector|
26
+ remove_empty_metadata(corrector, hash)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def remove_empty_metadata(corrector, node)
33
+ corrector.remove(
34
+ range_with_surrounding_comma(
35
+ range_with_surrounding_space(
36
+ node.source_range,
37
+ side: :left
38
+ ),
39
+ :left
40
+ )
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Use `eq` instead of `be ==` to compare objects.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(foo).to be == 42
11
+ #
12
+ # # good
13
+ # expect(foo).to eq 42
14
+ #
15
+ class Eq < Base
16
+ extend AutoCorrector
17
+ include RangeHelp
18
+
19
+ MSG = 'Use `eq` instead of `be ==` to compare objects.'
20
+ RESTRICT_ON_SEND = Runners.all
21
+
22
+ # @!method be_equals(node)
23
+ def_node_matcher :be_equals, <<~PATTERN
24
+ (send _ #Runners.all $(send (send nil? :be) :== _))
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ be_equals(node) do |matcher|
29
+ range = offense_range(matcher)
30
+ add_offense(range) do |corrector|
31
+ corrector.replace(range, 'eq')
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def offense_range(matcher)
39
+ range_between(
40
+ matcher.source_range.begin_pos,
41
+ matcher.loc.selector.end_pos
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -66,7 +66,9 @@ module RuboCop
66
66
 
67
67
  # @param text [String]
68
68
  def strip_excessive_whitespace(text)
69
- text.strip.gsub(/[[:blank:]]{2,}/, ' ')
69
+ text
70
+ .gsub(/[[:blank:]]{2,}/, ' ')
71
+ .gsub(/\A[[:blank:]]|[[:blank:]]\z/, '')
70
72
  end
71
73
 
72
74
  # @param node [RuboCop::AST::Node]
@@ -79,6 +79,8 @@ module RuboCop
79
79
  PATTERN
80
80
 
81
81
  def on_send(node)
82
+ return if node.chained? || node.each_ancestor(:def, :defs).any?
83
+
82
84
  focus_metadata(node) do |focus|
83
85
  add_offense(focus) do |corrector|
84
86
  if focus.pair_type? || focus.str_type? || focus.sym_type?
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Use consistent metadata style.
7
+ #
8
+ # This cop does not support autocorrection in the case of
9
+ # `EnforcedStyle: hash` where the trailing metadata type is ambiguous.
10
+ # (e.g. `describe 'Something', :a, b`)
11
+ #
12
+ # @example EnforcedStyle: symbol (default)
13
+ # # bad
14
+ # describe 'Something', a: true
15
+ #
16
+ # # good
17
+ # describe 'Something', :a
18
+ #
19
+ # @example EnforcedStyle: hash
20
+ # # bad
21
+ # describe 'Something', :a
22
+ #
23
+ # # good
24
+ # describe 'Something', a: true
25
+ class MetadataStyle < Base # rubocop:disable Metrics/ClassLength
26
+ extend AutoCorrector
27
+
28
+ include ConfigurableEnforcedStyle
29
+ include Metadata
30
+ include RangeHelp
31
+
32
+ # @!method extract_metadata_hash(node)
33
+ def_node_matcher :extract_metadata_hash, <<~PATTERN
34
+ (send _ _ _ ... $hash)
35
+ PATTERN
36
+
37
+ # @!method match_boolean_metadata_pair?(node)
38
+ def_node_matcher :match_boolean_metadata_pair?, <<~PATTERN
39
+ (pair sym true)
40
+ PATTERN
41
+
42
+ # @!method match_ambiguous_trailing_metadata?(node)
43
+ def_node_matcher :match_ambiguous_trailing_metadata?, <<~PATTERN
44
+ (send _ _ _ ... !{hash sym})
45
+ PATTERN
46
+
47
+ def on_metadata(symbols, hash)
48
+ symbols.each do |symbol|
49
+ on_metadata_symbol(symbol)
50
+ end
51
+
52
+ return unless hash
53
+
54
+ hash.pairs.each do |pair|
55
+ on_metadata_pair(pair)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def autocorrect_pair(corrector, node)
62
+ remove_pair(corrector, node)
63
+ insert_symbol(corrector, node)
64
+ end
65
+
66
+ def autocorrect_symbol(corrector, node)
67
+ return if match_ambiguous_trailing_metadata?(node.parent)
68
+
69
+ remove_symbol(corrector, node)
70
+ insert_pair(corrector, node)
71
+ end
72
+
73
+ def bad_metadata_pair?(node)
74
+ style == :symbol && match_boolean_metadata_pair?(node)
75
+ end
76
+
77
+ def bad_metadata_symbol?(_node)
78
+ style == :hash
79
+ end
80
+
81
+ def format_symbol_to_pair_source(node)
82
+ "#{node.value}: true"
83
+ end
84
+
85
+ def insert_pair(corrector, node)
86
+ hash_node = extract_metadata_hash(node.parent)
87
+ if hash_node.nil?
88
+ insert_pair_as_last_argument(corrector, node)
89
+ elsif hash_node.pairs.any?
90
+ insert_pair_to_non_empty_hash_metadata(corrector, node, hash_node)
91
+ else
92
+ insert_pair_to_empty_hash_metadata(corrector, node, hash_node)
93
+ end
94
+ end
95
+
96
+ def insert_pair_as_last_argument(corrector, node)
97
+ corrector.insert_before(
98
+ node.parent.location.end || node.parent.source_range.with(
99
+ begin_pos: node.parent.source_range.end_pos
100
+ ),
101
+ ", #{format_symbol_to_pair_source(node)}"
102
+ )
103
+ end
104
+
105
+ def insert_pair_to_empty_hash_metadata(corrector, node, hash_node)
106
+ corrector.insert_after(
107
+ hash_node.location.begin,
108
+ " #{format_symbol_to_pair_source(node)} "
109
+ )
110
+ end
111
+
112
+ def insert_pair_to_non_empty_hash_metadata(corrector, node, hash_node)
113
+ corrector.insert_after(
114
+ hash_node.children.last,
115
+ ", #{format_symbol_to_pair_source(node)}"
116
+ )
117
+ end
118
+
119
+ def insert_symbol(corrector, node)
120
+ corrector.insert_after(
121
+ node.parent.left_sibling,
122
+ ", #{node.key.value.inspect}"
123
+ )
124
+ end
125
+
126
+ def message_for_style
127
+ format(
128
+ 'Use %<style>s style for metadata.',
129
+ style: style
130
+ )
131
+ end
132
+
133
+ def on_metadata_pair(node)
134
+ return unless bad_metadata_pair?(node)
135
+
136
+ add_offense(node, message: message_for_style) do |corrector|
137
+ autocorrect_pair(corrector, node)
138
+ end
139
+ end
140
+
141
+ def on_metadata_symbol(node)
142
+ return unless bad_metadata_symbol?(node)
143
+
144
+ add_offense(node, message: message_for_style) do |corrector|
145
+ autocorrect_symbol(corrector, node)
146
+ end
147
+ end
148
+
149
+ def remove_pair(corrector, node)
150
+ if !node.parent.braces? || node.left_siblings.any?
151
+ remove_pair_following(corrector, node)
152
+ elsif node.right_siblings.any?
153
+ remove_pair_preceding(corrector, node)
154
+ else
155
+ corrector.remove(node)
156
+ end
157
+ end
158
+
159
+ def remove_pair_following(corrector, node)
160
+ corrector.remove(
161
+ range_with_surrounding_comma(
162
+ range_with_surrounding_space(
163
+ node.source_range,
164
+ side: :left
165
+ ),
166
+ :left
167
+ )
168
+ )
169
+ end
170
+
171
+ def remove_pair_preceding(corrector, node)
172
+ corrector.remove(
173
+ range_with_surrounding_space(
174
+ range_with_surrounding_comma(
175
+ node.source_range,
176
+ :right
177
+ ),
178
+ side: :right
179
+ )
180
+ )
181
+ end
182
+
183
+ def remove_symbol(corrector, node)
184
+ corrector.remove(
185
+ range_with_surrounding_comma(
186
+ range_with_surrounding_space(
187
+ node.source_range,
188
+ side: :left
189
+ ),
190
+ :left
191
+ )
192
+ )
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Help methods for file.
7
+ module FileHelp
8
+ def expanded_file_path
9
+ File.expand_path(processed_source.file_path)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -13,7 +13,7 @@ module RuboCop
13
13
  def_node_matcher :rspec_metadata, <<~PATTERN
14
14
  (block
15
15
  (send
16
- #rspec? {#Examples.all #ExampleGroups.all #SharedGroups.all #Hooks.all} _ ${send str sym}* (hash $...)?)
16
+ #rspec? {#Examples.all #ExampleGroups.all #SharedGroups.all #Hooks.all} _ $...)
17
17
  ...)
18
18
  PATTERN
19
19
 
@@ -24,25 +24,39 @@ module RuboCop
24
24
 
25
25
  # @!method metadata_in_block(node)
26
26
  def_node_search :metadata_in_block, <<~PATTERN
27
- (send (lvar %) #Hooks.all _ ${send str sym}* (hash $...)?)
27
+ (send (lvar %) #Hooks.all _ $...)
28
28
  PATTERN
29
29
 
30
30
  def on_block(node)
31
31
  rspec_configure(node) do |block_var|
32
- metadata_in_block(node, block_var) do |symbols, pairs|
33
- on_metadata(symbols, pairs.flatten)
32
+ metadata_in_block(node, block_var) do |metadata_arguments|
33
+ on_matadata_arguments(metadata_arguments)
34
34
  end
35
35
  end
36
36
 
37
- rspec_metadata(node) do |symbols, pairs|
38
- on_metadata(symbols, pairs.flatten)
37
+ rspec_metadata(node) do |metadata_arguments|
38
+ on_matadata_arguments(metadata_arguments)
39
39
  end
40
40
  end
41
41
  alias on_numblock on_block
42
42
 
43
- def on_metadata(_symbols, _pairs)
43
+ def on_metadata(_symbols, _hash)
44
44
  raise ::NotImplementedError
45
45
  end
46
+
47
+ private
48
+
49
+ def on_matadata_arguments(metadata_arguments)
50
+ *symbols, last = metadata_arguments
51
+ hash = nil
52
+ case last&.type
53
+ when :hash
54
+ hash = last
55
+ when :sym
56
+ symbols << last
57
+ end
58
+ on_metadata(symbols, hash)
59
+ end
46
60
  end
47
61
  end
48
62
  end
@@ -17,10 +17,12 @@ module RuboCop
17
17
  # # bad
18
18
  # it { is_expected.to have_http_status 200 }
19
19
  # it { is_expected.to have_http_status 404 }
20
+ # it { is_expected.to have_http_status "403" }
20
21
  #
21
22
  # # good
22
23
  # it { is_expected.to have_http_status :ok }
23
24
  # it { is_expected.to have_http_status :not_found }
25
+ # it { is_expected.to have_http_status :forbidden }
24
26
  # it { is_expected.to have_http_status :success }
25
27
  # it { is_expected.to have_http_status :error }
26
28
  #
@@ -28,10 +30,12 @@ module RuboCop
28
30
  # # bad
29
31
  # it { is_expected.to have_http_status :ok }
30
32
  # it { is_expected.to have_http_status :not_found }
33
+ # it { is_expected.to have_http_status "forbidden" }
31
34
  #
32
35
  # # good
33
36
  # it { is_expected.to have_http_status 200 }
34
37
  # it { is_expected.to have_http_status 404 }
38
+ # it { is_expected.to have_http_status 403 }
35
39
  # it { is_expected.to have_http_status :success }
36
40
  # it { is_expected.to have_http_status :error }
37
41
  #
@@ -39,8 +43,10 @@ module RuboCop
39
43
  # # bad
40
44
  # it { is_expected.to have_http_status :ok }
41
45
  # it { is_expected.to have_http_status :not_found }
46
+ # it { is_expected.to have_http_status "forbidden" }
42
47
  # it { is_expected.to have_http_status 200 }
43
48
  # it { is_expected.to have_http_status 404 }
49
+ # it { is_expected.to have_http_status "403" }
44
50
  #
45
51
  # # good
46
52
  # it { is_expected.to be_ok }
@@ -55,7 +61,7 @@ module RuboCop
55
61
 
56
62
  # @!method http_status(node)
57
63
  def_node_matcher :http_status, <<-PATTERN
58
- (send nil? :have_http_status ${int sym})
64
+ (send nil? :have_http_status ${int sym str})
59
65
  PATTERN
60
66
 
61
67
  def on_send(node)
@@ -124,7 +130,7 @@ module RuboCop
124
130
  end
125
131
 
126
132
  def current
127
- number.inspect
133
+ node.value.inspect
128
134
  end
129
135
 
130
136
  private
@@ -134,7 +140,7 @@ module RuboCop
134
140
  end
135
141
 
136
142
  def number
137
- node.source.to_i
143
+ node.source.delete('"').to_i
138
144
  end
139
145
  end
140
146
 
@@ -159,7 +165,7 @@ module RuboCop
159
165
  end
160
166
 
161
167
  def number
162
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
168
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol.to_sym]
163
169
  end
164
170
  end
165
171
 
@@ -177,8 +183,10 @@ module RuboCop
177
183
  def prefer
178
184
  if node.sym_type?
179
185
  "be_#{node.value}"
180
- else
186
+ elsif node.int_type?
181
187
  "be_#{symbol}"
188
+ elsif node.str_type?
189
+ "be_#{normalize_str}"
182
190
  end
183
191
  end
184
192
 
@@ -195,6 +203,15 @@ module RuboCop
195
203
  def number
196
204
  node.source.to_i
197
205
  end
206
+
207
+ def normalize_str
208
+ normalized = node.source.delete('"')
209
+ if normalized.match?(/\A\d+\z/)
210
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(normalized.to_i)
211
+ else
212
+ normalized
213
+ end
214
+ end
198
215
  end
199
216
  end
200
217
  end
@@ -17,6 +17,9 @@ module RuboCop
17
17
  # # good
18
18
  # expect(foo).not_to be_valid
19
19
  #
20
+ # # good (with method chain)
21
+ # expect(foo).to be_invalid.and be_odd
22
+ #
20
23
  # @example EnforcedStyle: be_invalid
21
24
  # # bad
22
25
  # expect(foo).not_to be_valid
@@ -24,6 +27,9 @@ module RuboCop
24
27
  # # good
25
28
  # expect(foo).to be_invalid
26
29
  #
30
+ # # good (with method chain)
31
+ # expect(foo).to be_invalid.or be_even
32
+ #
27
33
  class NegationBeValid < Base
28
34
  extend AutoCorrector
29
35
  include ConfigurableEnforcedStyle
@@ -124,7 +124,7 @@ module RuboCop
124
124
 
125
125
  def register_offense(item, repeated_lines, args)
126
126
  add_offense(item, message: message(repeated_lines)) do |corrector|
127
- if item.loc.line < repeated_lines.min
127
+ if item.loc.line > repeated_lines.max
128
128
  replace_to_receive_messages(corrector, item, args)
129
129
  else
130
130
  corrector.remove(item_range_by_whole_lines(item))
@@ -23,7 +23,8 @@ module RuboCop
23
23
 
24
24
  MSG = 'Sort metadata alphabetically.'
25
25
 
26
- def on_metadata(symbols, pairs)
26
+ def on_metadata(symbols, hash)
27
+ pairs = hash&.pairs || []
27
28
  return if sorted?(symbols, pairs)
28
29
 
29
30
  crime_scene = crime_scene(symbols, pairs)
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that spec file paths are consistent and well-formed.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # whatever_spec.rb # describe MyClass
11
+ # my_class_spec.rb # describe MyClass, '#method'
12
+ #
13
+ # # good
14
+ # my_class_spec.rb # describe MyClass
15
+ # my_class_method_spec.rb # describe MyClass, '#method'
16
+ # my_class/method_spec.rb # describe MyClass, '#method'
17
+ #
18
+ # @example `CustomTransform: {RuboCop=>rubocop, RSpec=>rspec}` (default)
19
+ # # good
20
+ # rubocop_spec.rb # describe RuboCop
21
+ # rspec_spec.rb # describe RSpec
22
+ #
23
+ # @example `IgnoreMethods: false` (default)
24
+ # # bad
25
+ # my_class_spec.rb # describe MyClass, '#method'
26
+ #
27
+ # @example `IgnoreMethods: true`
28
+ # # good
29
+ # my_class_spec.rb # describe MyClass, '#method'
30
+ #
31
+ # @example `IgnoreMetadata: {type=>routing}` (default)
32
+ # # good
33
+ # whatever_spec.rb # describe MyClass, type: :routing do; end
34
+ #
35
+ class SpecFilePathFormat < Base
36
+ include TopLevelGroup
37
+ include Namespace
38
+ include FileHelp
39
+
40
+ MSG = 'Spec path should end with `%<suffix>s`.'
41
+
42
+ # @!method example_group_arguments(node)
43
+ def_node_matcher :example_group_arguments, <<~PATTERN
44
+ (block (send #rspec? #ExampleGroups.all $_ $...) ...)
45
+ PATTERN
46
+
47
+ # @!method metadata_key_value(node)
48
+ def_node_search :metadata_key_value, '(pair (sym $_key) (sym $_value))'
49
+
50
+ def on_top_level_example_group(node)
51
+ return unless top_level_groups.one?
52
+
53
+ example_group_arguments(node) do |class_name, arguments|
54
+ next if !class_name.const_type? || ignore_metadata?(arguments)
55
+
56
+ ensure_correct_file_path(class_name, arguments)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def ensure_correct_file_path(class_name, arguments)
63
+ pattern = correct_path_pattern(class_name, arguments)
64
+ return if filename_ends_with?(pattern)
65
+
66
+ # For the suffix shown in the offense message, modify the regular
67
+ # expression pattern to resemble a glob pattern for clearer error
68
+ # messages.
69
+ suffix = pattern.sub('.*', '*').sub('[^/]*', '*').sub('\.', '.')
70
+ add_global_offense(format(MSG, suffix: suffix))
71
+ end
72
+
73
+ def ignore_metadata?(arguments)
74
+ arguments.any? do |argument|
75
+ metadata_key_value(argument).any? do |key, value|
76
+ ignore_metadata.values_at(key.to_s).include?(value.to_s)
77
+ end
78
+ end
79
+ end
80
+
81
+ def correct_path_pattern(class_name, arguments)
82
+ path = [expected_path(class_name)]
83
+ path << '.*' unless ignore?(arguments.first)
84
+ path << [name_pattern(arguments.first), '[^/]*_spec\.rb']
85
+ path.join
86
+ end
87
+
88
+ def name_pattern(method_name)
89
+ return if ignore?(method_name)
90
+
91
+ method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')
92
+ end
93
+
94
+ def ignore?(method_name)
95
+ !method_name&.str_type? || ignore_methods?
96
+ end
97
+
98
+ def expected_path(constant)
99
+ constants = namespace(constant) + constant.const_name.split('::')
100
+
101
+ File.join(
102
+ constants.map do |name|
103
+ custom_transform.fetch(name) { camel_to_snake_case(name) }
104
+ end
105
+ )
106
+ end
107
+
108
+ def camel_to_snake_case(string)
109
+ string
110
+ .gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
111
+ .gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
112
+ .downcase
113
+ end
114
+
115
+ def custom_transform
116
+ cop_config.fetch('CustomTransform', {})
117
+ end
118
+
119
+ def ignore_methods?
120
+ cop_config['IgnoreMethods']
121
+ end
122
+
123
+ def ignore_metadata
124
+ cop_config.fetch('IgnoreMetadata', {})
125
+ end
126
+
127
+ def filename_ends_with?(pattern)
128
+ expanded_file_path.match?("#{pattern}$")
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that spec file paths suffix are consistent and well-formed.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # my_class/foo_specorb.rb # describe MyClass
11
+ # spec/models/user.rb # describe User
12
+ # spec/models/user_specxrb # describe User
13
+ #
14
+ # # good
15
+ # my_class_spec.rb # describe MyClass
16
+ #
17
+ # # good - shared examples are allowed
18
+ # spec/models/user.rb # shared_examples_for 'foo'
19
+ #
20
+ class SpecFilePathSuffix < Base
21
+ include TopLevelGroup
22
+ include FileHelp
23
+
24
+ MSG = 'Spec path should end with `_spec.rb`.'
25
+
26
+ def on_top_level_example_group(node)
27
+ example_group?(node) do
28
+ add_global_offense(MSG) unless correct_path?
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def correct_path?
35
+ expanded_file_path.end_with?('_spec.rb')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -35,7 +35,7 @@ module RuboCop
35
35
  return unless inside_example_group?(node)
36
36
 
37
37
  variable_definition?(node) do |variable|
38
- next unless style_violation?(variable)
38
+ next unless style_offense?(variable)
39
39
 
40
40
  add_offense(
41
41
  variable,
@@ -59,7 +59,7 @@ module RuboCop
59
59
  end
60
60
  end
61
61
 
62
- def style_violation?(variable)
62
+ def style_offense?(variable)
63
63
  style == :symbols && string?(variable) ||
64
64
  style == :strings && symbol?(variable)
65
65
  end
@@ -79,8 +79,8 @@ module RuboCop
79
79
  expression = class_reference.source_range
80
80
 
81
81
  add_offense(expression, message: message) do |corrector|
82
- violation = class_reference.source
83
- corrector.replace(expression, correct_style(violation))
82
+ offense = class_reference.source
83
+ corrector.replace(expression, correct_style(offense))
84
84
 
85
85
  opposite_style_detected
86
86
  end
@@ -98,11 +98,11 @@ module RuboCop
98
98
  class_reference_style != style
99
99
  end
100
100
 
101
- def correct_style(violation)
101
+ def correct_style(offense)
102
102
  if style == :string
103
- "'#{violation}'"
103
+ "'#{offense}'"
104
104
  else
105
- violation.gsub(/^['"]|['"]$/, '')
105
+ offense.gsub(/^['"]|['"]$/, '')
106
106
  end
107
107
  end
108
108
  end
@@ -57,6 +57,8 @@ require_relative 'rspec/empty_line_after_example_group'
57
57
  require_relative 'rspec/empty_line_after_final_let'
58
58
  require_relative 'rspec/empty_line_after_hook'
59
59
  require_relative 'rspec/empty_line_after_subject'
60
+ require_relative 'rspec/empty_metadata'
61
+ require_relative 'rspec/eq'
60
62
  require_relative 'rspec/example_length'
61
63
  require_relative 'rspec/example_without_description'
62
64
  require_relative 'rspec/example_wording'
@@ -86,6 +88,7 @@ require_relative 'rspec/match_array'
86
88
  require_relative 'rspec/message_chain'
87
89
  require_relative 'rspec/message_expectation'
88
90
  require_relative 'rspec/message_spies'
91
+ require_relative 'rspec/metadata_style'
89
92
  require_relative 'rspec/missing_example_group_argument'
90
93
  require_relative 'rspec/multiple_describes'
91
94
  require_relative 'rspec/multiple_expectations'
@@ -116,6 +119,8 @@ require_relative 'rspec/shared_examples'
116
119
  require_relative 'rspec/single_argument_message_chain'
117
120
  require_relative 'rspec/skip_block_inside_example'
118
121
  require_relative 'rspec/sort_metadata'
122
+ require_relative 'rspec/spec_file_path_format'
123
+ require_relative 'rspec/spec_file_path_suffix'
119
124
  require_relative 'rspec/stubbed_mock'
120
125
  require_relative 'rspec/subject_declaration'
121
126
  require_relative 'rspec/subject_stub'
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Version information for the RSpec RuboCop plugin.
6
6
  module Version
7
- STRING = '2.23.2'
7
+ STRING = '2.24.0'
8
8
  end
9
9
  end
10
10
  end
data/lib/rubocop-rspec.rb CHANGED
@@ -17,6 +17,7 @@ require_relative 'rubocop/rspec/wording'
17
17
  # Dependent on `RuboCop::RSpec::Language::NodePattern`.
18
18
  require_relative 'rubocop/rspec/language'
19
19
 
20
+ require_relative 'rubocop/cop/rspec/mixin/file_help'
20
21
  require_relative 'rubocop/cop/rspec/mixin/final_end_location'
21
22
  require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
22
23
  require_relative 'rubocop/cop/rspec/mixin/location_help'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.23.2
4
+ version: 2.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-08-09 00:00:00.000000000 Z
13
+ date: 2023-09-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -112,6 +112,8 @@ files:
112
112
  - lib/rubocop/cop/rspec/empty_line_after_final_let.rb
113
113
  - lib/rubocop/cop/rspec/empty_line_after_hook.rb
114
114
  - lib/rubocop/cop/rspec/empty_line_after_subject.rb
115
+ - lib/rubocop/cop/rspec/empty_metadata.rb
116
+ - lib/rubocop/cop/rspec/eq.rb
115
117
  - lib/rubocop/cop/rspec/example_length.rb
116
118
  - lib/rubocop/cop/rspec/example_without_description.rb
117
119
  - lib/rubocop/cop/rspec/example_wording.rb
@@ -147,9 +149,11 @@ files:
147
149
  - lib/rubocop/cop/rspec/message_chain.rb
148
150
  - lib/rubocop/cop/rspec/message_expectation.rb
149
151
  - lib/rubocop/cop/rspec/message_spies.rb
152
+ - lib/rubocop/cop/rspec/metadata_style.rb
150
153
  - lib/rubocop/cop/rspec/missing_example_group_argument.rb
151
154
  - lib/rubocop/cop/rspec/mixin/comments_help.rb
152
155
  - lib/rubocop/cop/rspec/mixin/empty_line_separation.rb
156
+ - lib/rubocop/cop/rspec/mixin/file_help.rb
153
157
  - lib/rubocop/cop/rspec/mixin/final_end_location.rb
154
158
  - lib/rubocop/cop/rspec/mixin/inside_example_group.rb
155
159
  - lib/rubocop/cop/rspec/mixin/location_help.rb
@@ -194,6 +198,8 @@ files:
194
198
  - lib/rubocop/cop/rspec/single_argument_message_chain.rb
195
199
  - lib/rubocop/cop/rspec/skip_block_inside_example.rb
196
200
  - lib/rubocop/cop/rspec/sort_metadata.rb
201
+ - lib/rubocop/cop/rspec/spec_file_path_format.rb
202
+ - lib/rubocop/cop/rspec/spec_file_path_suffix.rb
197
203
  - lib/rubocop/cop/rspec/stubbed_mock.rb
198
204
  - lib/rubocop/cop/rspec/subject_declaration.rb
199
205
  - lib/rubocop/cop/rspec/subject_stub.rb