rubocop-rspec 2.21.0 → 2.23.0

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