rubocop-rspec 1.38.0 → 1.42.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +1 -61
  5. data/config/default.yml +147 -17
  6. data/lib/rubocop-rspec.rb +3 -1
  7. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +11 -18
  8. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +11 -18
  9. data/lib/rubocop/cop/rspec/be.rb +1 -1
  10. data/lib/rubocop/cop/rspec/be_eql.rb +5 -5
  11. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +18 -16
  12. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +8 -9
  13. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  14. data/lib/rubocop/cop/rspec/context_method.rb +5 -7
  15. data/lib/rubocop/cop/rspec/cop.rb +9 -29
  16. data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
  17. data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
  18. data/lib/rubocop/cop/rspec/described_class.rb +10 -7
  19. data/lib/rubocop/cop/rspec/dialect.rb +4 -11
  20. data/lib/rubocop/cop/rspec/empty_hook.rb +46 -0
  21. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -3
  22. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -5
  23. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +4 -1
  24. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -5
  25. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +4 -1
  26. data/lib/rubocop/cop/rspec/example_wording.rb +6 -7
  27. data/lib/rubocop/cop/rspec/expect_actual.rb +7 -10
  28. data/lib/rubocop/cop/rspec/expect_change.rb +9 -34
  29. data/lib/rubocop/cop/rspec/expect_in_hook.rb +2 -2
  30. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  31. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +23 -20
  32. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +10 -16
  33. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +6 -7
  34. data/lib/rubocop/cop/rspec/file_path.rb +32 -4
  35. data/lib/rubocop/cop/rspec/hook_argument.rb +11 -17
  36. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +9 -28
  37. data/lib/rubocop/cop/rspec/implicit_expect.rb +6 -14
  38. data/lib/rubocop/cop/rspec/implicit_subject.rb +8 -5
  39. data/lib/rubocop/cop/rspec/instance_spy.rb +17 -11
  40. data/lib/rubocop/cop/rspec/instance_variable.rb +3 -7
  41. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +2 -5
  42. data/lib/rubocop/cop/rspec/it_behaves_like.rb +4 -5
  43. data/lib/rubocop/cop/rspec/leading_subject.rb +12 -19
  44. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
  45. data/lib/rubocop/cop/rspec/let_before_examples.rb +9 -25
  46. data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
  47. data/lib/rubocop/cop/rspec/message_chain.rb +6 -5
  48. data/lib/rubocop/cop/rspec/message_expectation.rb +1 -1
  49. data/lib/rubocop/cop/rspec/message_spies.rb +1 -2
  50. data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -18
  51. data/lib/rubocop/cop/rspec/named_subject.rb +7 -7
  52. data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
  53. data/lib/rubocop/cop/rspec/not_to_not.rb +4 -5
  54. data/lib/rubocop/cop/rspec/predicate_matcher.rb +25 -55
  55. data/lib/rubocop/cop/rspec/rails/http_status.rb +6 -8
  56. data/lib/rubocop/cop/rspec/receive_counts.rb +14 -16
  57. data/lib/rubocop/cop/rspec/receive_never.rb +10 -10
  58. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +11 -1
  59. data/lib/rubocop/cop/rspec/return_from_stub.rb +11 -21
  60. data/lib/rubocop/cop/rspec/scattered_let.rb +11 -1
  61. data/lib/rubocop/cop/rspec/shared_context.rb +7 -20
  62. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -8
  63. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +14 -17
  64. data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
  65. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  66. data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
  67. data/lib/rubocop/cop/rspec/yield.rb +13 -10
  68. data/lib/rubocop/cop/rspec_cops.rb +5 -1
  69. data/lib/rubocop/rspec/blank_line_separation.rb +0 -8
  70. data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
  71. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  72. data/lib/rubocop/rspec/example.rb +1 -1
  73. data/lib/rubocop/rspec/example_group.rb +21 -49
  74. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  75. data/lib/rubocop/rspec/language.rb +8 -0
  76. data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
  77. data/lib/rubocop/rspec/top_level_group.rb +44 -0
  78. data/lib/rubocop/rspec/variable.rb +16 -0
  79. data/lib/rubocop/rspec/version.rb +1 -1
  80. metadata +17 -10
  81. data/lib/rubocop/rspec/util.rb +0 -19
@@ -25,6 +25,7 @@ module RuboCop
25
25
  # # good
26
26
  # 3.times { create :user }
27
27
  class CreateList < Cop
28
+ extend AutoCorrector
28
29
  include ConfigurableEnforcedStyle
29
30
 
30
31
  MSG_CREATE_LIST = 'Prefer create_list.'
@@ -51,26 +52,19 @@ module RuboCop
51
52
  return unless n_times_block_without_arg?(node)
52
53
  return unless contains_only_factory?(node.body)
53
54
 
54
- add_offense(node.send_node, message: MSG_CREATE_LIST)
55
+ add_offense(node.send_node, message: MSG_CREATE_LIST) do |corrector|
56
+ CreateListCorrector.new(node.send_node).call(corrector)
57
+ end
55
58
  end
56
59
 
57
60
  def on_send(node)
58
61
  return unless style == :n_times
59
62
 
60
63
  factory_list_call(node) do |_receiver, _factory, count, _|
61
- add_offense(
62
- node,
63
- location: :selector,
64
- message: format(MSG_N_TIMES, number: count)
65
- )
66
- end
67
- end
68
-
69
- def autocorrect(node)
70
- if style == :create_list
71
- CreateListCorrector.new(node)
72
- else
73
- TimesCorrector.new(node)
64
+ message = format(MSG_N_TIMES, number: count)
65
+ add_offense(node.loc.selector, message: message) do |corrector|
66
+ TimesCorrector.new(node).call(corrector)
67
+ end
74
68
  end
75
69
  end
76
70
 
@@ -115,7 +109,7 @@ module RuboCop
115
109
 
116
110
  def call(corrector)
117
111
  replacement = generate_n_times_block(node)
118
- corrector.replace(node.loc.expression, replacement)
112
+ corrector.replace(node, replacement)
119
113
  end
120
114
 
121
115
  private
@@ -148,7 +142,7 @@ module RuboCop
148
142
  call_replacement(node)
149
143
  end
150
144
 
151
- corrector.replace(node.loc.expression, replacement)
145
+ corrector.replace(node, replacement)
152
146
  end
153
147
 
154
148
  private
@@ -20,6 +20,8 @@ module RuboCop
20
20
  # factory :foo, class: 'Foo' do
21
21
  # end
22
22
  class FactoryClassName < Cop
23
+ extend AutoCorrector
24
+
23
25
  MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
24
26
  'constant.'
25
27
  ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze
@@ -32,13 +34,10 @@ module RuboCop
32
34
  class_name(node) do |cn|
33
35
  next if allowed?(cn.const_name)
34
36
 
35
- add_offense(cn, message: format(MSG, class_name: cn.const_name))
36
- end
37
- end
38
-
39
- def autocorrect(node)
40
- lambda do |corrector|
41
- corrector.replace(node.loc.expression, "'#{node.source}'")
37
+ msg = format(MSG, class_name: cn.const_name)
38
+ add_offense(cn, message: msg) do |corrector|
39
+ corrector.replace(cn, "'#{cn.source}'")
40
+ end
42
41
  end
43
42
  end
44
43
 
@@ -3,10 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks that spec file paths are consistent with the test subject.
6
+ # Checks that spec file paths are consistent and well-formed.
7
7
  #
8
- # Checks the path of the spec file and enforces that it reflects the
9
- # described class/module and its optionally called out method.
8
+ # By default, this checks that spec file paths are consistent with the
9
+ # test subject and and enforces that it reflects the described
10
+ # class/module and its optionally called out method.
10
11
  #
11
12
  # With the configuration option `IgnoreMethods` the called out method will
12
13
  # be ignored when determining the enforced path.
@@ -15,6 +16,10 @@ module RuboCop
15
16
  # be specified that should not as usual be transformed from CamelCase to
16
17
  # snake_case (e.g. 'RuboCop' => 'rubocop' ).
17
18
  #
19
+ # With the configuration option `SpecSuffixOnly` test files will only
20
+ # be checked to ensure they end in '_spec.rb'. This option disables
21
+ # checking for consistency in the test subject or test methods.
22
+ #
18
23
  # @example
19
24
  # # bad
20
25
  # whatever_spec.rb # describe MyClass
@@ -41,6 +46,16 @@ module RuboCop
41
46
  # # good
42
47
  # my_class_spec.rb # describe MyClass, '#method'
43
48
  #
49
+ # @example when configuration is `SpecSuffixOnly: true`
50
+ # # good
51
+ # whatever_spec.rb # describe MyClass
52
+ #
53
+ # # good
54
+ # my_class_spec.rb # describe MyClass
55
+ #
56
+ # # good
57
+ # my_class_spec.rb # describe MyClass, '#method'
58
+ #
44
59
  class FilePath < Cop
45
60
  include RuboCop::RSpec::TopLevelDescribe
46
61
 
@@ -70,9 +85,15 @@ module RuboCop
70
85
  end
71
86
 
72
87
  def glob_for((described_class, method_name))
88
+ return glob_for_spec_suffix_only? if spec_suffix_only?
89
+
73
90
  "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
74
91
  end
75
92
 
93
+ def glob_for_spec_suffix_only?
94
+ '*_spec.rb'
95
+ end
96
+
76
97
  def name_glob(name)
77
98
  return unless name&.str_type?
78
99
 
@@ -103,12 +124,19 @@ module RuboCop
103
124
  end
104
125
 
105
126
  def filename_ends_with?(glob)
106
- File.fnmatch?("*#{glob}", processed_source.buffer.name)
127
+ filename =
128
+ RuboCop::PathUtil.relative_path(processed_source.buffer.name)
129
+ .gsub('../', '')
130
+ File.fnmatch?("*#{glob}", filename)
107
131
  end
108
132
 
109
133
  def relevant_rubocop_rspec_file?(_file)
110
134
  true
111
135
  end
136
+
137
+ def spec_suffix_only?
138
+ cop_config['SpecSuffixOnly']
139
+ end
112
140
  end
113
141
  end
114
142
  end
@@ -58,6 +58,7 @@ module RuboCop
58
58
  # # ...
59
59
  # end
60
60
  class HookArgument < Cop
61
+ extend AutoCorrector
61
62
  include ConfigurableEnforcedStyle
62
63
 
63
64
  IMPLICIT_MSG = 'Omit the default `%<scope>p` ' \
@@ -78,18 +79,11 @@ module RuboCop
78
79
  return check_implicit(method_send) unless scope_name
79
80
 
80
81
  style_detected(scope_name)
81
- add_offense(
82
- method_send,
83
- message: explicit_message(scope_name)
84
- )
85
- end
86
- end
87
-
88
- def autocorrect(node)
89
- scope = implicit_style? ? '' : "(#{style.inspect})"
90
-
91
- lambda do |corrector|
92
- corrector.replace(argument_range(node), scope)
82
+ msg = explicit_message(scope_name)
83
+ add_offense(method_send, message: msg) do |corrector|
84
+ scope = implicit_style? ? '' : "(#{style.inspect})"
85
+ corrector.replace(argument_range(method_send), scope)
86
+ end
93
87
  end
94
88
  end
95
89
 
@@ -99,11 +93,11 @@ module RuboCop
99
93
  style_detected(:implicit)
100
94
  return if implicit_style?
101
95
 
102
- add_offense(
103
- method_send,
104
- location: :selector,
105
- message: format(EXPLICIT_MSG, scope: style)
106
- )
96
+ msg = explicit_message(nil)
97
+ add_offense(method_send.loc.selector, message: msg) do |corrector|
98
+ scope = "(#{style.inspect})"
99
+ corrector.replace(argument_range(method_send), scope)
100
+ end
107
101
  end
108
102
 
109
103
  def explicit_message(scope)
@@ -24,8 +24,7 @@ module RuboCop
24
24
  # end
25
25
  #
26
26
  class HooksBeforeExamples < Cop
27
- include RangeHelp
28
- include RuboCop::RSpec::FinalEndLocation
27
+ extend AutoCorrector
29
28
 
30
29
  MSG = 'Move `%<hook>s` above the examples in the group.'
31
30
 
@@ -42,17 +41,6 @@ module RuboCop
42
41
  check_hooks(node.body) if multiline_block?(node.body)
43
42
  end
44
43
 
45
- def autocorrect(node)
46
- lambda do |corrector|
47
- first_example = find_first_example(node.parent)
48
- first_example_pos = first_example.loc.expression
49
- indent = "\n" + ' ' * first_example.loc.column
50
-
51
- corrector.insert_before(first_example_pos, source(node) + indent)
52
- corrector.remove(node_range_with_surrounding_space(node))
53
- end
54
- end
55
-
56
44
  private
57
45
 
58
46
  def multiline_block?(block)
@@ -67,10 +55,10 @@ module RuboCop
67
55
  next if child.sibling_index < first_example.sibling_index
68
56
  next unless hook?(child)
69
57
 
70
- add_offense(
71
- child,
72
- message: format(MSG, hook: child.method_name)
73
- )
58
+ msg = format(MSG, hook: child.method_name)
59
+ add_offense(child, message: msg) do |corrector|
60
+ autocorrect(corrector, child, first_example)
61
+ end
74
62
  end
75
63
  end
76
64
 
@@ -78,17 +66,10 @@ module RuboCop
78
66
  node.children.find { |sibling| example_or_group?(sibling) }
79
67
  end
80
68
 
81
- def node_range_with_surrounding_space(node)
82
- range = node_range(node)
83
- range_by_whole_lines(range, include_final_newline: true)
84
- end
85
-
86
- def source(node)
87
- node_range(node).source
88
- end
89
-
90
- def node_range(node)
91
- node.loc.expression.with(end_pos: final_end_location(node).end_pos)
69
+ def autocorrect(corrector, node, first_example)
70
+ RuboCop::RSpec::Corrector::MoveNode.new(
71
+ node, corrector, processed_source
72
+ ).move_before(first_example)
92
73
  end
93
74
  end
94
75
  end
@@ -25,6 +25,7 @@ module RuboCop
25
25
  # it { should be_truthy }
26
26
  #
27
27
  class ImplicitExpect < Cop
28
+ extend AutoCorrector
28
29
  include ConfigurableEnforcedStyle
29
30
 
30
31
  MSG = 'Prefer `%<good>s` over `%<bad>s`.'
@@ -54,20 +55,11 @@ module RuboCop
54
55
  else
55
56
  opposite_style_detected
56
57
 
57
- add_offense(
58
- node,
59
- location: source_range,
60
- message: offense_message(expectation_source)
61
- )
62
- end
63
- end
64
-
65
- def autocorrect(node)
66
- lambda do |corrector|
67
- offense = offending_expect(node)
68
- replacement = replacement_source(offense.source)
69
-
70
- corrector.replace(offense, replacement)
58
+ msg = offense_message(expectation_source)
59
+ add_offense(source_range, message: msg) do |corrector|
60
+ replacement = replacement_source(expectation_source)
61
+ corrector.replace(source_range, replacement)
62
+ end
71
63
  end
72
64
  end
73
65
 
@@ -27,6 +27,7 @@ module RuboCop
27
27
  # it { expect(subject).to be_truthy }
28
28
  #
29
29
  class ImplicitSubject < Cop
30
+ extend AutoCorrector
30
31
  include ConfigurableEnforcedStyle
31
32
 
32
33
  MSG = "Don't use implicit subject."
@@ -39,10 +40,14 @@ module RuboCop
39
40
  return unless implicit_subject?(node)
40
41
  return if valid_usage?(node)
41
42
 
42
- add_offense(node)
43
+ add_offense(node) do |corrector|
44
+ autocorrect(corrector, node)
45
+ end
43
46
  end
44
47
 
45
- def autocorrect(node)
48
+ private
49
+
50
+ def autocorrect(corrector, node)
46
51
  replacement = 'expect(subject)'
47
52
  if node.method_name == :should
48
53
  replacement += '.to'
@@ -50,11 +55,9 @@ module RuboCop
50
55
  replacement += '.not_to'
51
56
  end
52
57
 
53
- ->(corrector) { corrector.replace(node.loc.selector, replacement) }
58
+ corrector.replace(node.loc.selector, replacement)
54
59
  end
55
60
 
56
- private
57
-
58
61
  def valid_usage?(node)
59
62
  example = node.ancestors.find { |parent| example?(parent) }
60
63
  return false if example.nil?
@@ -19,6 +19,8 @@ module RuboCop
19
19
  # end
20
20
  #
21
21
  class InstanceSpy < Cop
22
+ extend AutoCorrector
23
+
22
24
  MSG = 'Use `instance_spy` when you check your double '\
23
25
  'with `have_received`.'
24
26
 
@@ -43,22 +45,26 @@ module RuboCop
43
45
 
44
46
  null_double(node) do |var, receiver|
45
47
  have_received_usage(node) do |expected|
46
- add_offense(receiver) if expected == var
48
+ next if expected != var
49
+
50
+ add_offense(receiver) do |corrector|
51
+ autocorrect(corrector, receiver)
52
+ end
47
53
  end
48
54
  end
49
55
  end
50
56
 
51
- def autocorrect(node)
52
- lambda do |corrector|
53
- replacement = 'instance_spy'
54
- corrector.replace(node.loc.selector, replacement)
57
+ private
55
58
 
56
- double_source_map = node.parent.loc
57
- as_null_object_range = double_source_map
58
- .dot
59
- .join(double_source_map.selector)
60
- corrector.remove(as_null_object_range)
61
- end
59
+ def autocorrect(corrector, node)
60
+ replacement = 'instance_spy'
61
+ corrector.replace(node.loc.selector, replacement)
62
+
63
+ double_source_map = node.parent.loc
64
+ as_null_object_range = double_source_map
65
+ .dot
66
+ .join(double_source_map.selector)
67
+ corrector.remove(as_null_object_range)
62
68
  end
63
69
  end
64
70
  end
@@ -47,13 +47,11 @@ module RuboCop
47
47
  # end
48
48
  #
49
49
  class InstanceVariable < Cop
50
+ include RuboCop::RSpec::TopLevelGroup
51
+
50
52
  MSG = 'Avoid instance variables – use let, ' \
51
53
  'a method call, or a local variable (if possible).'
52
54
 
53
- EXAMPLE_GROUP_METHODS = ExampleGroups::ALL + SharedGroups::ALL
54
-
55
- def_node_matcher :spec_group?, EXAMPLE_GROUP_METHODS.block_pattern
56
-
57
55
  def_node_matcher :dynamic_class?, <<-PATTERN
58
56
  (block (send (const nil? :Class) :new ...) ...)
59
57
  PATTERN
@@ -69,9 +67,7 @@ module RuboCop
69
67
 
70
68
  def_node_search :ivar_assigned?, '(ivasgn % ...)'
71
69
 
72
- def on_block(node)
73
- return unless spec_group?(node)
74
-
70
+ def on_top_level_group(node)
75
71
  ivar_usage(node) do |ivar, name|
76
72
  next if valid_usage?(ivar)
77
73
  next if assignment_only? && !ivar_assigned?(node, name)
@@ -24,7 +24,8 @@ module RuboCop
24
24
 
25
25
  def on_send(node)
26
26
  invalid_predicate_matcher?(node) do |predicate|
27
- add_offense(predicate)
27
+ add_offense(predicate,
28
+ message: format(MSG, matcher: predicate.method_name))
28
29
  end
29
30
  end
30
31
 
@@ -34,10 +35,6 @@ module RuboCop
34
35
  name = name.to_s
35
36
  name.start_with?('be_', 'have_') && name.end_with?('?')
36
37
  end
37
-
38
- def message(predicate)
39
- format(MSG, matcher: predicate.method_name)
40
- end
41
38
  end
42
39
  end
43
40
  end
@@ -19,6 +19,7 @@ module RuboCop
19
19
  # # good
20
20
  # it_should_behave_like 'a foo'
21
21
  class ItBehavesLike < Cop
22
+ extend AutoCorrector
22
23
  include ConfigurableEnforcedStyle
23
24
 
24
25
  MSG = 'Prefer `%<replacement>s` over `%<original>s` when including '\
@@ -28,14 +29,12 @@ module RuboCop
28
29
 
29
30
  def on_send(node)
30
31
  example_inclusion_offense(node, alternative_style) do
31
- add_offense(node)
32
+ add_offense(node) do |corrector|
33
+ corrector.replace(node.loc.selector, style.to_s)
34
+ end
32
35
  end
33
36
  end
34
37
 
35
- def autocorrect(node)
36
- ->(corrector) { corrector.replace(node.loc.selector, style.to_s) }
37
- end
38
-
39
38
  private
40
39
 
41
40
  def message(_node)