rubocop-rspec 2.21.0 → 2.23.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +1 -1
  4. data/config/default.yml +37 -16
  5. data/config/obsoletion.yml +6 -0
  6. data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -0
  7. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +11 -4
  8. data/lib/rubocop/cop/rspec/expect_actual.rb +2 -2
  9. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +25 -118
  10. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +40 -107
  11. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +30 -250
  12. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +19 -46
  13. data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +23 -64
  14. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +45 -79
  15. data/lib/rubocop/cop/rspec/focus.rb +13 -0
  16. data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
  17. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  18. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
  19. data/lib/rubocop/cop/rspec/let_before_examples.rb +4 -0
  20. data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
  21. data/lib/rubocop/cop/rspec/pending.rb +12 -2
  22. data/lib/rubocop/cop/rspec/predicate_matcher.rb +3 -3
  23. data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +92 -0
  24. data/lib/rubocop/cop/rspec/receive_messages.rb +155 -0
  25. data/lib/rubocop/cop/rspec/verified_double_reference.rb +1 -1
  26. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  27. data/lib/rubocop/cop/rspec_cops.rb +2 -0
  28. data/lib/rubocop/rspec/config_formatter.rb +6 -0
  29. data/lib/rubocop/rspec/version.rb +1 -1
  30. data/lib/rubocop-rspec.rb +1 -3
  31. metadata +18 -4
  32. data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
  33. data/lib/rubocop/rspec/factory_bot.rb +0 -64
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # Anonymous classes are fine, since they don't result in global
18
18
  # namespace name clashes.
19
19
  #
20
- # @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants
20
+ # @see https://rspec.info/features/3-12/rspec-mocks/mutating-constants
21
21
  #
22
22
  # @example Constants leak between examples
23
23
  # # bad
@@ -51,6 +51,10 @@ module RuboCop
51
51
  }
52
52
  PATTERN
53
53
 
54
+ def self.autocorrect_incompatible_with
55
+ [RSpec::ScatteredLet]
56
+ end
57
+
54
58
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
55
59
  return unless example_group_with_body?(node)
56
60
 
@@ -145,7 +145,7 @@ module RuboCop
145
145
  end
146
146
 
147
147
  def find_subject(block_node)
148
- block_node.body.child_nodes.find { |send_node| subject?(send_node) }
148
+ block_node.body&.child_nodes&.find { |send_node| subject?(send_node) }
149
149
  end
150
150
  end
151
151
  end
@@ -41,10 +41,15 @@ module RuboCop
41
41
  def_node_matcher :skippable?, <<~PATTERN
42
42
  {
43
43
  (send #rspec? #ExampleGroups.regular ...)
44
- (send nil? #Examples.regular ...)
44
+ #skippable_example?
45
45
  }
46
46
  PATTERN
47
47
 
48
+ # @!method skippable_example?(node)
49
+ def_node_matcher :skippable_example?, <<~PATTERN
50
+ (send nil? #Examples.regular ...)
51
+ PATTERN
52
+
48
53
  # @!method pending_block?(node)
49
54
  def_node_matcher :pending_block?, <<~PATTERN
50
55
  {
@@ -62,7 +67,12 @@ module RuboCop
62
67
  private
63
68
 
64
69
  def skipped?(node)
65
- skippable?(node) && skipped_in_metadata?(node)
70
+ skippable?(node) && skipped_in_metadata?(node) ||
71
+ skipped_regular_example_without_body?(node)
72
+ end
73
+
74
+ def skipped_regular_example_without_body?(node)
75
+ skippable_example?(node) && !node.block_node
66
76
  end
67
77
  end
68
78
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
  name[0..-2]
75
75
  when 'exist?', 'exists?'
76
76
  'exist'
77
- when /^has_/
77
+ when /\Ahas_/
78
78
  name.sub('has_', 'have_')[0..-2]
79
79
  else
80
80
  "be_#{name[0..-2]}"
@@ -240,10 +240,10 @@ module RuboCop
240
240
  'include?'
241
241
  when 'respond_to'
242
242
  'respond_to?'
243
- when /^have_(.+)/
243
+ when /\Ahave_(.+)/
244
244
  "has_#{Regexp.last_match(1)}?"
245
245
  else
246
- "#{matcher[/^be_(.+)/, 1]}?"
246
+ "#{matcher[/\Abe_(.+)/, 1]}?"
247
247
  end
248
248
  end
249
249
  # rubocop:enable Metrics/MethodLength
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Rails
7
+ # Enforces use of `be_invalid` or `not_to` for negated be_valid.
8
+ #
9
+ # @example EnforcedStyle: not_to (default)
10
+ # # bad
11
+ # expect(foo).to be_invalid
12
+ #
13
+ # # good
14
+ # expect(foo).not_to be_valid
15
+ #
16
+ # @example EnforcedStyle: be_invalid
17
+ # # bad
18
+ # expect(foo).not_to be_valid
19
+ #
20
+ # # good
21
+ # expect(foo).to be_invalid
22
+ #
23
+ class NegationBeValid < Base
24
+ extend AutoCorrector
25
+ include ConfigurableEnforcedStyle
26
+
27
+ MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
28
+ RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze
29
+
30
+ # @!method not_to?(node)
31
+ def_node_matcher :not_to?, <<~PATTERN
32
+ (send ... :not_to (send nil? :be_valid ...))
33
+ PATTERN
34
+
35
+ # @!method be_invalid?(node)
36
+ def_node_matcher :be_invalid?, <<~PATTERN
37
+ (send ... :to (send nil? :be_invalid ...))
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ return unless offense?(node.parent)
42
+
43
+ add_offense(offense_range(node),
44
+ message: message(node.method_name)) do |corrector|
45
+ corrector.replace(node.parent.loc.selector, replaced_runner)
46
+ corrector.replace(node.loc.selector, replaced_matcher)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def offense?(node)
53
+ case style
54
+ when :not_to
55
+ be_invalid?(node)
56
+ when :be_invalid
57
+ not_to?(node)
58
+ end
59
+ end
60
+
61
+ def offense_range(node)
62
+ node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
63
+ end
64
+
65
+ def message(_matcher)
66
+ format(MSG,
67
+ runner: replaced_runner,
68
+ matcher: replaced_matcher)
69
+ end
70
+
71
+ def replaced_runner
72
+ case style
73
+ when :not_to
74
+ 'not_to'
75
+ when :be_invalid
76
+ 'to'
77
+ end
78
+ end
79
+
80
+ def replaced_matcher
81
+ case style
82
+ when :not_to
83
+ 'be_valid'
84
+ when :be_invalid
85
+ 'be_invalid'
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for multiple messages stubbed on the same object.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # before do
11
+ # allow(Service).to receive(:foo).and_return(bar)
12
+ # allow(Service).to receive(:baz).and_return(qux)
13
+ # end
14
+ #
15
+ # # good
16
+ # before do
17
+ # allow(Service).to receive_messages(foo: bar, baz: qux)
18
+ # end
19
+ #
20
+ # # good - ignore same message
21
+ # before do
22
+ # allow(Service).to receive(:foo).and_return(bar)
23
+ # allow(Service).to receive(:foo).and_return(qux)
24
+ # end
25
+ #
26
+ class ReceiveMessages < Base
27
+ extend AutoCorrector
28
+ include RangeHelp
29
+
30
+ MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
31
+ '%<loc>s.'
32
+
33
+ # @!method allow_receive_message?(node)
34
+ def_node_matcher :allow_receive_message?, <<~PATTERN
35
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc?))
36
+ PATTERN
37
+
38
+ # @!method allow_argument(node)
39
+ def_node_matcher :allow_argument, <<~PATTERN
40
+ (send (send nil? :allow $_ ...) ...)
41
+ PATTERN
42
+
43
+ # @!method receive_node(node)
44
+ def_node_search :receive_node, <<~PATTERN
45
+ $(send (send nil? :receive ...) ...)
46
+ PATTERN
47
+
48
+ # @!method receive_arg(node)
49
+ def_node_search :receive_arg, <<~PATTERN
50
+ (send (send nil? :receive $_) ...)
51
+ PATTERN
52
+
53
+ # @!method receive_and_return_argument(node)
54
+ def_node_matcher :receive_and_return_argument, <<~PATTERN
55
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
56
+ PATTERN
57
+
58
+ def on_begin(node)
59
+ repeated_receive_message(node).each do |item, repeated_lines, args|
60
+ next if repeated_lines.empty?
61
+
62
+ register_offense(item, repeated_lines, args)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def repeated_receive_message(node)
69
+ node
70
+ .children
71
+ .select { |child| allow_receive_message?(child) }
72
+ .group_by { |child| allow_argument(child) }
73
+ .values
74
+ .reject(&:one?)
75
+ .flat_map { |items| add_repeated_lines_and_arguments(items) }
76
+ end
77
+
78
+ def add_repeated_lines_and_arguments(items)
79
+ uniq_items = uniq_items(items)
80
+ repeated_lines = uniq_items.map(&:first_line)
81
+ uniq_items.map do |item|
82
+ [item, repeated_lines - [item.first_line], arguments(uniq_items)]
83
+ end
84
+ end
85
+
86
+ def uniq_items(items)
87
+ items.select do |item|
88
+ items.none? do |i|
89
+ receive_arg(item).first == receive_arg(i).first &&
90
+ !same_line?(item, i)
91
+ end
92
+ end
93
+ end
94
+
95
+ def arguments(items)
96
+ items.map do |item|
97
+ receive_and_return_argument(item) do |receive_arg, return_arg|
98
+ "#{normalize_receive_arg(receive_arg)}: " \
99
+ "#{normalize_return_arg(return_arg)}"
100
+ end
101
+ end
102
+ end
103
+
104
+ def normalize_receive_arg(receive_arg)
105
+ if requires_quotes?(receive_arg)
106
+ "'#{receive_arg}'"
107
+ else
108
+ receive_arg
109
+ end
110
+ end
111
+
112
+ def normalize_return_arg(return_arg)
113
+ if return_arg.hash_type? && !return_arg.braces?
114
+ "{ #{return_arg.source} }"
115
+ else
116
+ return_arg.source
117
+ end
118
+ end
119
+
120
+ def register_offense(item, repeated_lines, args)
121
+ add_offense(item, message: message(repeated_lines)) do |corrector|
122
+ if item.loc.line < repeated_lines.min
123
+ replace_to_receive_messages(corrector, item, args)
124
+ else
125
+ corrector.remove(item_range_by_whole_lines(item))
126
+ end
127
+ end
128
+ end
129
+
130
+ def message(repeated_lines)
131
+ format(MSG, loc: repeated_lines)
132
+ end
133
+
134
+ def replace_to_receive_messages(corrector, item, args)
135
+ receive_node(item) do |node|
136
+ corrector.replace(node,
137
+ "receive_messages(#{args.join(', ')})")
138
+ end
139
+ end
140
+
141
+ def item_range_by_whole_lines(item)
142
+ range_by_whole_lines(item.source_range, include_final_newline: true)
143
+ end
144
+
145
+ def heredoc?(node)
146
+ (node.str_type? || node.dstr_type?) && node.heredoc?
147
+ end
148
+
149
+ def requires_quotes?(value)
150
+ value.match?(/^:".*?"|=$/)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  #
8
8
  # Only investigates references that are one of the supported styles.
9
9
  #
10
- # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
10
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
11
11
  #
12
12
  # This cop can be configured in your configuration using the
13
13
  # `EnforcedStyle` option and supports `--auto-gen-config`.
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module RSpec
6
6
  # Prefer using verifying doubles over normal doubles.
7
7
  #
8
- # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
8
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
9
9
  #
10
10
  # @example
11
11
  # # bad
@@ -18,6 +18,7 @@ require_relative 'rspec/factory_bot/syntax_methods'
18
18
 
19
19
  require_relative 'rspec/rails/avoid_setup_hook'
20
20
  require_relative 'rspec/rails/have_http_status'
21
+ require_relative 'rspec/rails/negation_be_valid'
21
22
  begin
22
23
  require_relative 'rspec/rails/http_status'
23
24
  rescue LoadError
@@ -99,6 +100,7 @@ require_relative 'rspec/pending'
99
100
  require_relative 'rspec/pending_without_reason'
100
101
  require_relative 'rspec/predicate_matcher'
101
102
  require_relative 'rspec/receive_counts'
103
+ require_relative 'rspec/receive_messages'
102
104
  require_relative 'rspec/receive_never'
103
105
  require_relative 'rspec/redundant_around'
104
106
  require_relative 'rspec/repeated_description'
@@ -16,6 +16,12 @@ module RuboCop
16
16
  RSpec/Capybara/SpecificFinders
17
17
  RSpec/Capybara/SpecificMatcher
18
18
  RSpec/Capybara/VisibilityMatcher
19
+ RSpec/FactoryBot/AttributeDefinedStatically
20
+ RSpec/FactoryBot/ConsistentParenthesesStyle
21
+ RSpec/FactoryBot/CreateList
22
+ RSpec/FactoryBot/FactoryClassName
23
+ RSpec/FactoryBot/FactoryNameStyle
24
+ RSpec/FactoryBot/SyntaxMethods
19
25
  )
20
26
  AMENDMENTS = %(Metrics/BlockLength)
21
27
  COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/'
@@ -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.21.0'
7
+ STRING = '2.23.0'
8
8
  end
9
9
  end
10
10
  end
data/lib/rubocop-rspec.rb CHANGED
@@ -5,6 +5,7 @@ require 'yaml'
5
5
 
6
6
  require 'rubocop'
7
7
  require 'rubocop-capybara'
8
+ require 'rubocop-factory_bot'
8
9
 
9
10
  require_relative 'rubocop/rspec'
10
11
  require_relative 'rubocop/rspec/inject'
@@ -16,8 +17,6 @@ require_relative 'rubocop/rspec/wording'
16
17
  # Dependent on `RuboCop::RSpec::Language::NodePattern`.
17
18
  require_relative 'rubocop/rspec/language'
18
19
 
19
- require_relative 'rubocop/rspec/factory_bot/language'
20
-
21
20
  require_relative 'rubocop/cop/rspec/mixin/final_end_location'
22
21
  require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
23
22
  require_relative 'rubocop/cop/rspec/mixin/location_help'
@@ -37,7 +36,6 @@ require_relative 'rubocop/rspec/concept'
37
36
  require_relative 'rubocop/rspec/corrector/move_node'
38
37
  require_relative 'rubocop/rspec/example'
39
38
  require_relative 'rubocop/rspec/example_group'
40
- require_relative 'rubocop/rspec/factory_bot'
41
39
  require_relative 'rubocop/rspec/hook'
42
40
 
43
41
  RuboCop::RSpec::Inject.defaults!
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.21.0
4
+ version: 2.23.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-05-05 00:00:00.000000000 Z
13
+ date: 2023-07-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -40,6 +40,20 @@ dependencies:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
42
  version: '2.17'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rubocop-factory_bot
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '2.22'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '2.22'
43
57
  description: |2
44
58
  Code style checking for RSpec files.
45
59
  A plugin for the RuboCop code style enforcing & linting tool.
@@ -161,8 +175,10 @@ files:
161
175
  - lib/rubocop/cop/rspec/rails/http_status.rb
162
176
  - lib/rubocop/cop/rspec/rails/inferred_spec_type.rb
163
177
  - lib/rubocop/cop/rspec/rails/minitest_assertions.rb
178
+ - lib/rubocop/cop/rspec/rails/negation_be_valid.rb
164
179
  - lib/rubocop/cop/rspec/rails/travel_around.rb
165
180
  - lib/rubocop/cop/rspec/receive_counts.rb
181
+ - lib/rubocop/cop/rspec/receive_messages.rb
166
182
  - lib/rubocop/cop/rspec/receive_never.rb
167
183
  - lib/rubocop/cop/rspec/redundant_around.rb
168
184
  - lib/rubocop/cop/rspec/repeated_description.rb
@@ -197,8 +213,6 @@ files:
197
213
  - lib/rubocop/rspec/description_extractor.rb
198
214
  - lib/rubocop/rspec/example.rb
199
215
  - lib/rubocop/rspec/example_group.rb
200
- - lib/rubocop/rspec/factory_bot.rb
201
- - lib/rubocop/rspec/factory_bot/language.rb
202
216
  - lib/rubocop/rspec/hook.rb
203
217
  - lib/rubocop/rspec/inject.rb
204
218
  - lib/rubocop/rspec/language.rb
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module RSpec
5
- module FactoryBot
6
- # Contains node matchers for common FactoryBot DSL.
7
- module Language
8
- extend RuboCop::NodePattern::Macros
9
-
10
- METHODS = %i[
11
- attributes_for
12
- attributes_for_list
13
- attributes_for_pair
14
- build
15
- build_list
16
- build_pair
17
- build_stubbed
18
- build_stubbed_list
19
- build_stubbed_pair
20
- create
21
- create_list
22
- create_pair
23
- generate
24
- generate_list
25
- null
26
- null_list
27
- null_pair
28
- ].to_set.freeze
29
-
30
- # @!method factory_bot?(node)
31
- def_node_matcher :factory_bot?, <<~PATTERN
32
- (const {nil? cbase} {:FactoryGirl :FactoryBot})
33
- PATTERN
34
- end
35
- end
36
- end
37
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module RSpec
5
- # RuboCop FactoryBot project namespace
6
- module FactoryBot
7
- ATTRIBUTE_DEFINING_METHODS = %i[
8
- factory
9
- ignore
10
- trait
11
- traits_for_enum
12
- transient
13
- ].freeze
14
-
15
- UNPROXIED_METHODS = %i[
16
- __send__
17
- __id__
18
- nil?
19
- send
20
- object_id
21
- extend
22
- instance_eval
23
- initialize
24
- block_given?
25
- raise
26
- caller
27
- method
28
- ].freeze
29
-
30
- DEFINITION_PROXY_METHODS = %i[
31
- add_attribute
32
- after
33
- association
34
- before
35
- callback
36
- ignore
37
- initialize_with
38
- sequence
39
- skip_create
40
- to_create
41
- ].freeze
42
-
43
- RESERVED_METHODS =
44
- DEFINITION_PROXY_METHODS +
45
- UNPROXIED_METHODS +
46
- ATTRIBUTE_DEFINING_METHODS
47
-
48
- private_constant(
49
- :ATTRIBUTE_DEFINING_METHODS,
50
- :UNPROXIED_METHODS,
51
- :DEFINITION_PROXY_METHODS,
52
- :RESERVED_METHODS
53
- )
54
-
55
- def self.attribute_defining_methods
56
- ATTRIBUTE_DEFINING_METHODS
57
- end
58
-
59
- def self.reserved_methods
60
- RESERVED_METHODS
61
- end
62
- end
63
- end
64
- end