rubocop-rspec 1.38.0 → 1.42.0

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