rubocop-rspec 2.23.2 → 2.24.0

Sign up to get free protection for your applications and to get access to all the features.
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