rubocop-rspec 2.16.0 → 2.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +124 -9
  3. data/README.md +3 -3
  4. data/config/default.yml +145 -18
  5. data/config/obsoletion.yml +15 -0
  6. data/lib/rubocop/cop/rspec/be_empty.rb +44 -0
  7. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  8. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +29 -115
  9. data/lib/rubocop/cop/rspec/capybara/match_style.rb +38 -0
  10. data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +23 -96
  11. data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +19 -75
  12. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +14 -83
  13. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +25 -69
  14. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +26 -63
  15. data/lib/rubocop/cop/rspec/change_by_zero.rb +33 -23
  16. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  17. data/lib/rubocop/cop/rspec/context_method.rb +5 -1
  18. data/lib/rubocop/cop/rspec/context_wording.rb +13 -6
  19. data/lib/rubocop/cop/rspec/describe_method.rb +16 -8
  20. data/lib/rubocop/cop/rspec/described_class.rb +2 -1
  21. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +7 -5
  22. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  23. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +2 -2
  24. data/lib/rubocop/cop/rspec/empty_example_group.rb +10 -7
  25. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -2
  26. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  27. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  28. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  29. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  30. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
  31. data/lib/rubocop/cop/rspec/expect_actual.rb +4 -4
  32. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  33. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +25 -118
  34. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +40 -107
  35. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +30 -250
  36. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +19 -46
  37. data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +23 -64
  38. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +45 -79
  39. data/lib/rubocop/cop/rspec/file_path.rb +8 -2
  40. data/lib/rubocop/cop/rspec/focus.rb +19 -5
  41. data/lib/rubocop/cop/rspec/hook_argument.rb +12 -9
  42. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +5 -3
  43. data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
  44. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  45. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
  46. data/lib/rubocop/cop/rspec/let_before_examples.rb +8 -4
  47. data/lib/rubocop/cop/rspec/let_setup.rb +6 -8
  48. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  49. data/lib/rubocop/cop/rspec/metadata_style.rb +197 -0
  50. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +1 -2
  51. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  52. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  53. data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
  54. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +20 -4
  55. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -1
  56. data/lib/rubocop/cop/rspec/named_subject.rb +7 -5
  57. data/lib/rubocop/cop/rspec/no_expectation_example.rb +2 -5
  58. data/lib/rubocop/cop/rspec/overwriting_setup.rb +3 -1
  59. data/lib/rubocop/cop/rspec/pending.rb +23 -13
  60. data/lib/rubocop/cop/rspec/pending_without_reason.rb +72 -36
  61. data/lib/rubocop/cop/rspec/predicate_matcher.rb +49 -40
  62. data/lib/rubocop/cop/rspec/rails/have_http_status.rb +11 -6
  63. data/lib/rubocop/cop/rspec/rails/http_status.rb +107 -34
  64. data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +4 -4
  65. data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +60 -0
  66. data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
  67. data/lib/rubocop/cop/rspec/rails/travel_around.rb +92 -0
  68. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  69. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  70. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  71. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +3 -6
  72. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +3 -6
  73. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -4
  74. data/lib/rubocop/cop/rspec/scattered_setup.rb +23 -6
  75. data/lib/rubocop/cop/rspec/shared_context.rb +12 -13
  76. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -4
  77. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  78. data/lib/rubocop/cop/rspec/sort_metadata.rb +4 -3
  79. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  80. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  81. data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
  82. data/lib/rubocop/cop/rspec/subject_stub.rb +0 -1
  83. data/lib/rubocop/cop/rspec/variable_definition.rb +5 -2
  84. data/lib/rubocop/cop/rspec/variable_name.rb +4 -1
  85. data/lib/rubocop/cop/rspec/verified_double_reference.rb +7 -7
  86. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  87. data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
  88. data/lib/rubocop/cop/rspec_cops.rb +16 -0
  89. data/lib/rubocop/rspec/config_formatter.rb +16 -0
  90. data/lib/rubocop/rspec/example_group.rb +6 -8
  91. data/lib/rubocop/rspec/language/node_pattern.rb +26 -0
  92. data/lib/rubocop/rspec/language.rb +25 -16
  93. data/lib/rubocop/rspec/version.rb +1 -1
  94. data/lib/rubocop-rspec.rb +4 -5
  95. metadata +50 -8
  96. data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +0 -80
  97. data/lib/rubocop/cop/rspec/mixin/css_selector.rb +0 -146
  98. data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
  99. data/lib/rubocop/rspec/factory_bot.rb +0 -64
@@ -4,69 +4,32 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module Capybara
7
- # Checks for boolean visibility in Capybara finders.
8
- #
9
- # Capybara lets you find elements that match a certain visibility using
10
- # the `:visible` option. `:visible` accepts both boolean and symbols as
11
- # values, however using booleans can have unwanted effects. `visible:
12
- # false` does not find just invisible elements, but both visible and
13
- # invisible elements. For expressiveness and clarity, use one of the
14
- # symbol values, `:all`, `:hidden` or `:visible`.
15
- # Read more in
16
- # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
17
- #
18
- # @example
19
- # # bad
20
- # expect(page).to have_selector('.foo', visible: false)
21
- # expect(page).to have_css('.foo', visible: true)
22
- # expect(page).to have_link('my link', visible: false)
23
- #
24
- # # good
25
- # expect(page).to have_selector('.foo', visible: :visible)
26
- # expect(page).to have_css('.foo', visible: :all)
27
- # expect(page).to have_link('my link', visible: :hidden)
28
- #
29
- class VisibilityMatcher < ::RuboCop::Cop::Base
30
- MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
31
- MSG_TRUE = 'Use `:visible` instead of `true`.'
32
- CAPYBARA_MATCHER_METHODS = %w[
33
- button
34
- checked_field
35
- css
36
- field
37
- link
38
- select
39
- selector
40
- table
41
- unchecked_field
42
- xpath
43
- ].flat_map do |element|
44
- ["have_#{element}".to_sym, "have_no_#{element}".to_sym]
45
- end
46
-
47
- RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
48
-
49
- # @!method visible_true?(node)
50
- def_node_matcher :visible_true?, <<~PATTERN
51
- (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
52
- PATTERN
53
-
54
- # @!method visible_false?(node)
55
- def_node_matcher :visible_false?, <<~PATTERN
56
- (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
57
- PATTERN
58
-
59
- def on_send(node)
60
- visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
61
- visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
62
- end
63
-
64
- private
65
-
66
- def capybara_matcher?(method_name)
67
- CAPYBARA_MATCHER_METHODS.include? method_name
68
- end
69
- end
7
+ # @!parse
8
+ # # Checks for boolean visibility in Capybara finders.
9
+ # #
10
+ # # Capybara lets you find elements that match a certain visibility
11
+ # # using the `:visible` option. `:visible` accepts both boolean and
12
+ # # symbols as values, however using booleans can have unwanted
13
+ # # effects. `visible: false` does not find just invisible elements,
14
+ # # but both visible and invisible elements. For expressiveness and
15
+ # # clarity, use one of the # symbol values, `:all`, `:hidden` or
16
+ # # `:visible`.
17
+ # # Read more in
18
+ # # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
19
+ # #
20
+ # # @example
21
+ # # # bad
22
+ # # expect(page).to have_selector('.foo', visible: false)
23
+ # # expect(page).to have_css('.foo', visible: true)
24
+ # # expect(page).to have_link('my link', visible: false)
25
+ # #
26
+ # # # good
27
+ # # expect(page).to have_selector('.foo', visible: :visible)
28
+ # # expect(page).to have_css('.foo', visible: :all)
29
+ # # expect(page).to have_link('my link', visible: :hidden)
30
+ # #
31
+ # class VisibilityMatcher < ::RuboCop::Cop::Base; end
32
+ VisibilityMatcher = ::RuboCop::Cop::Capybara::VisibilityMatcher
70
33
  end
71
34
  end
72
35
  end
@@ -59,15 +59,16 @@ module RuboCop
59
59
  #
60
60
  class ChangeByZero < Base
61
61
  extend AutoCorrector
62
- MSG = 'Prefer `not_to change` over `to change.by(0)`.'
62
+ MSG = 'Prefer `not_to change` over `to %<method>s.by(0)`.'
63
63
  MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
64
- 'over `change.by(0)`.'
65
- RESTRICT_ON_SEND = %i[change].freeze
64
+ 'over `%<method>s.by(0)`.'
65
+ CHANGE_METHODS = Set[:change, :a_block_changing, :changing].freeze
66
+ RESTRICT_ON_SEND = CHANGE_METHODS.freeze
66
67
 
67
68
  # @!method expect_change_with_arguments(node)
68
69
  def_node_matcher :expect_change_with_arguments, <<-PATTERN
69
70
  (send
70
- (send nil? :change ...) :by
71
+ $(send nil? CHANGE_METHODS ...) :by
71
72
  (int 0))
72
73
  PATTERN
73
74
 
@@ -75,49 +76,62 @@ module RuboCop
75
76
  def_node_matcher :expect_change_with_block, <<-PATTERN
76
77
  (send
77
78
  (block
78
- (send nil? :change)
79
+ $(send nil? CHANGE_METHODS)
79
80
  (args)
80
- (send (...) $_)) :by
81
+ (send (...) _)) :by
81
82
  (int 0))
82
83
  PATTERN
83
84
 
84
85
  # @!method change_nodes(node)
85
86
  def_node_search :change_nodes, <<-PATTERN
86
- $(send nil? :change ...)
87
+ $(send nil? CHANGE_METHODS ...)
87
88
  PATTERN
88
89
 
89
90
  def on_send(node)
90
- expect_change_with_arguments(node.parent) do
91
- check_offense(node.parent)
91
+ expect_change_with_arguments(node.parent) do |change|
92
+ register_offense(node.parent, change)
92
93
  end
93
94
 
94
- expect_change_with_block(node.parent.parent) do
95
- check_offense(node.parent.parent)
95
+ expect_change_with_block(node.parent.parent) do |change|
96
+ register_offense(node.parent.parent, change)
96
97
  end
97
98
  end
98
99
 
99
100
  private
100
101
 
101
- def check_offense(node)
102
- expression = node.loc.expression
102
+ # rubocop:disable Metrics/MethodLength
103
+ def register_offense(node, change_node)
103
104
  if compound_expectations?(node)
104
- add_offense(expression, message: message_compound) do |corrector|
105
+ add_offense(node.source_range,
106
+ message: message_compound(change_node)) do |corrector|
105
107
  autocorrect_compound(corrector, node)
106
108
  end
107
109
  else
108
- add_offense(expression) do |corrector|
109
- autocorrect(corrector, node)
110
+ add_offense(node.source_range,
111
+ message: message(change_node)) do |corrector|
112
+ autocorrect(corrector, node, change_node)
110
113
  end
111
114
  end
112
115
  end
116
+ # rubocop:enable Metrics/MethodLength
113
117
 
114
118
  def compound_expectations?(node)
115
119
  %i[and or & |].include?(node.parent.method_name)
116
120
  end
117
121
 
118
- def autocorrect(corrector, node)
122
+ def message(change_node)
123
+ format(MSG, method: change_node.method_name)
124
+ end
125
+
126
+ def message_compound(change_node)
127
+ format(MSG_COMPOUND, preferred: preferred_method,
128
+ method: change_node.method_name)
129
+ end
130
+
131
+ def autocorrect(corrector, node, change_node)
119
132
  corrector.replace(node.parent.loc.selector, 'not_to')
120
- range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
133
+ corrector.replace(change_node.loc.selector, 'change')
134
+ range = node.loc.dot.with(end_pos: node.source_range.end_pos)
121
135
  corrector.remove(range)
122
136
  end
123
137
 
@@ -126,7 +140,7 @@ module RuboCop
126
140
 
127
141
  change_nodes(node) do |change_node|
128
142
  corrector.replace(change_node.loc.selector, negated_matcher)
129
- range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
143
+ range = node.loc.dot.with(end_pos: node.source_range.end_pos)
130
144
  corrector.remove(range)
131
145
  end
132
146
  end
@@ -135,10 +149,6 @@ module RuboCop
135
149
  cop_config['NegatedMatcher']
136
150
  end
137
151
 
138
- def message_compound
139
- format(MSG_COMPOUND, preferred: preferred_method)
140
- end
141
-
142
152
  def preferred_method
143
153
  negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
144
154
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks where `contain_exactly` is used.
7
+ #
8
+ # This cop checks for the following:
9
+ # - Prefer `match_array` when matching array values.
10
+ # - Prefer `be_empty` when using `contain_exactly` with no arguments.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # it { is_expected.to contain_exactly(*array1, *array2) }
15
+ #
16
+ # # good
17
+ # it { is_expected.to match_array(array1 + array2) }
18
+ #
19
+ # # good
20
+ # it { is_expected.to contain_exactly(content, *array) }
21
+ #
22
+ class ContainExactly < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = 'Prefer `match_array` when matching array values.'
26
+ RESTRICT_ON_SEND = %i[contain_exactly].freeze
27
+
28
+ def on_send(node)
29
+ return if node.arguments.empty?
30
+
31
+ check_populated_collection(node)
32
+ end
33
+
34
+ private
35
+
36
+ def check_populated_collection(node)
37
+ return unless node.each_child_node.all?(&:splat_type?)
38
+
39
+ add_offense(node) do |corrector|
40
+ autocorrect_for_populated_array(node, corrector)
41
+ end
42
+ end
43
+
44
+ def autocorrect_for_populated_array(node, corrector)
45
+ arrays = node.arguments.map do |splat_node|
46
+ splat_node.children.first
47
+ end
48
+ corrector.replace(
49
+ node,
50
+ "match_array(#{arrays.map(&:source).join(' + ')})"
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -31,7 +31,11 @@ module RuboCop
31
31
 
32
32
  # @!method context_method(node)
33
33
  def_node_matcher :context_method, <<-PATTERN
34
- (block (send #rspec? :context $(str #method_name?) ...) ...)
34
+ (block
35
+ (send #rspec? :context
36
+ ${(str #method_name?) (dstr (str #method_name?) ...)}
37
+ ...)
38
+ ...)
35
39
  PATTERN
36
40
 
37
41
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
@@ -10,7 +10,6 @@ module RuboCop
10
10
  # include `if`, `unless`, `for`, `before`, `after`, or `during`.
11
11
  # They may consist of multiple words if desired.
12
12
  #
13
- # @see https://rspec.rubystyle.guide/#context-descriptions
14
13
  # @see http://www.betterspecs.org/#contexts
15
14
  #
16
15
  # @example `Prefixes` configuration
@@ -63,12 +62,12 @@ module RuboCop
63
62
 
64
63
  # @!method context_wording(node)
65
64
  def_node_matcher :context_wording, <<-PATTERN
66
- (block (send #rspec? { :context :shared_context } $(str $_) ...) ...)
65
+ (block (send #rspec? { :context :shared_context } $({str dstr xstr} ...) ...) ...)
67
66
  PATTERN
68
67
 
69
68
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
70
- context_wording(node) do |context, description|
71
- if bad_pattern?(description)
69
+ context_wording(node) do |context|
70
+ if bad_pattern?(context)
72
71
  message = format(MSG, patterns: expect_patterns)
73
72
  add_offense(context, message: message)
74
73
  end
@@ -85,10 +84,18 @@ module RuboCop
85
84
  @prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ }
86
85
  end
87
86
 
88
- def bad_pattern?(description)
87
+ def bad_pattern?(node)
89
88
  return false if allowed_patterns.empty?
90
89
 
91
- !matches_allowed_pattern?(description)
90
+ !matches_allowed_pattern?(description(node))
91
+ end
92
+
93
+ def description(context)
94
+ if context.xstr_type?
95
+ context.value.value
96
+ else
97
+ context.value
98
+ end
92
99
  end
93
100
 
94
101
  def expect_patterns
@@ -23,20 +23,28 @@ module RuboCop
23
23
  MSG = 'The second argument to describe should be the method ' \
24
24
  "being tested. '#instance' or '.class'."
25
25
 
26
- # @!method second_argument(node)
27
- def_node_matcher :second_argument, <<~PATTERN
26
+ # @!method second_string_literal_argument(node)
27
+ def_node_matcher :second_string_literal_argument, <<~PATTERN
28
28
  (block
29
- (send #rspec? :describe _first_argument $(str _) ...) ...
30
- )
29
+ (send #rspec? :describe _first_argument ${str dstr} ...)
30
+ ...)
31
+ PATTERN
32
+
33
+ # @!method method_name?(node)
34
+ def_node_matcher :method_name?, <<~PATTERN
35
+ {(str #method_name_prefix?) (dstr (str #method_name_prefix?) ...)}
31
36
  PATTERN
32
37
 
33
38
  def on_top_level_group(node)
34
- second_argument = second_argument(node)
39
+ second_string_literal_argument(node) do |argument|
40
+ add_offense(argument) unless method_name?(argument)
41
+ end
42
+ end
35
43
 
36
- return unless second_argument
37
- return if second_argument.str_content.start_with?('#', '.')
44
+ private
38
45
 
39
- add_offense(second_argument)
46
+ def method_name_prefix?(description)
47
+ description.start_with?('.', '#')
40
48
  end
41
49
  end
42
50
  end
@@ -68,7 +68,8 @@ module RuboCop
68
68
  PATTERN
69
69
 
70
70
  # @!method rspec_block?(node)
71
- def_node_matcher :rspec_block?, block_pattern('#ALL.all')
71
+ def_node_matcher :rspec_block?,
72
+ '({block numblock} (send #rspec? #ALL.all ...) ...)'
72
73
 
73
74
  # @!method scope_changing_syntax?(node)
74
75
  def_node_matcher :scope_changing_syntax?, '{def class module}'
@@ -22,13 +22,15 @@ module RuboCop
22
22
  class DescribedClassModuleWrapping < Base
23
23
  MSG = 'Avoid opening modules and defining specs within them.'
24
24
 
25
- # @!method find_rspec_blocks(node)
26
- def_node_search :find_rspec_blocks, block_pattern('#ExampleGroups.all')
25
+ # @!method include_rspec_blocks?(node)
26
+ def_node_search :include_rspec_blocks?, <<~PATTERN
27
+ ({block numblock} (send #explicit_rspec? #ExampleGroups.all ...) ...)
28
+ PATTERN
27
29
 
28
30
  def on_module(node)
29
- find_rspec_blocks(node) do
30
- add_offense(node)
31
- end
31
+ return unless include_rspec_blocks?(node)
32
+
33
+ add_offense(node)
32
34
  end
33
35
  end
34
36
  end
@@ -49,7 +49,7 @@ module RuboCop
49
49
  MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
50
50
 
51
51
  # @!method rspec_method?(node)
52
- def_node_matcher :rspec_method?, send_pattern('#ALL.all')
52
+ def_node_matcher :rspec_method?, '(send #rspec? #ALL.all ...)'
53
53
 
54
54
  def on_send(node)
55
55
  return unless rspec_method?(node)
@@ -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
@@ -39,7 +39,7 @@ module RuboCop
39
39
  corrector.remove(
40
40
  range_with_surrounding_comma(
41
41
  range_with_surrounding_space(
42
- node.location.expression,
42
+ node.source_range,
43
43
  side: :left
44
44
  ),
45
45
  :left
@@ -53,7 +53,7 @@ module RuboCop
53
53
  # @param node [RuboCop::AST::Node]
54
54
  # @yield [RuboCop::AST::Node] example group body
55
55
  def_node_matcher :example_group_body, <<~PATTERN
56
- (block #{send_pattern('#ExampleGroups.all')} args $_)
56
+ (block (send #rspec? #ExampleGroups.all ...) args $_)
57
57
  PATTERN
58
58
 
59
59
  # @!method example_or_group_or_include?(node)
@@ -72,10 +72,10 @@ module RuboCop
72
72
  # @return [Array<RuboCop::AST::Node>] matching nodes
73
73
  def_node_matcher :example_or_group_or_include?, <<~PATTERN
74
74
  {
75
- #{block_pattern(
76
- '{#Examples.all #ExampleGroups.all #Includes.all}'
77
- )}
78
- #{send_pattern('{#Examples.all #Includes.all}')}
75
+ (block
76
+ (send #rspec? {#Examples.all #ExampleGroups.all #Includes.all} ...)
77
+ ...)
78
+ (send nil? {#Examples.all #Includes.all} ...)
79
79
  }
80
80
  PATTERN
81
81
 
@@ -95,7 +95,7 @@ module RuboCop
95
95
  # @param node [RuboCop::AST::Node]
96
96
  # @return [Array<RuboCop::AST::Node>] matching nodes
97
97
  def_node_matcher :examples_inside_block?, <<~PATTERN
98
- (block !#{send_pattern('#Hooks.all')} _ #examples?)
98
+ (block !(send nil? #Hooks.all ...) _ #examples?)
99
99
  PATTERN
100
100
 
101
101
  # @!method examples_directly_or_in_block?(node)
@@ -131,6 +131,7 @@ module RuboCop
131
131
  {
132
132
  #examples_directly_or_in_block?
133
133
  (begin <#examples_directly_or_in_block? ...>)
134
+ (begin <#examples_in_branches? ...>)
134
135
  }
135
136
  PATTERN
136
137
 
@@ -169,12 +170,14 @@ module RuboCop
169
170
  end
170
171
 
171
172
  def examples_in_branches?(condition_node)
173
+ return if !condition_node.if_type? && !condition_node.case_type?
174
+
172
175
  condition_node.branches.any? { |branch| examples?(branch) }
173
176
  end
174
177
 
175
178
  def removed_range(node)
176
179
  range_by_whole_lines(
177
- node.location.expression,
180
+ node.source_range,
178
181
  include_final_newline: true
179
182
  )
180
183
  end
@@ -31,14 +31,14 @@ module RuboCop
31
31
 
32
32
  # @!method empty_hook?(node)
33
33
  def_node_matcher :empty_hook?, <<~PATTERN
34
- (block $#{send_pattern('#Hooks.all')} _ nil?)
34
+ (block $(send nil? #Hooks.all ...) _ nil?)
35
35
  PATTERN
36
36
 
37
37
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
38
38
  empty_hook?(node) do |hook|
39
39
  add_offense(hook) do |corrector|
40
40
  corrector.remove(
41
- range_with_surrounding_space(node.loc.expression, side: :left)
41
+ range_with_surrounding_space(node.source_range, side: :left)
42
42
  )
43
43
  end
44
44
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
  MSG = 'Add an empty line after `%<example_group>s`.'
31
31
 
32
32
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
33
- return unless example_group?(node)
33
+ return unless spec_group?(node)
34
34
 
35
35
  missing_separating_line_offense(node) do |method|
36
36
  format(MSG, example_group: method)
@@ -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
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
 
90
90
  def docstring(node)
91
- expr = node.loc.expression
91
+ expr = node.source_range
92
92
 
93
93
  Parser::Source::Range.new(
94
94
  expr.source_buffer,
@@ -52,14 +52,23 @@ module RuboCop
52
52
 
53
53
  # @param text [String]
54
54
  def excessive_whitespace?(text)
55
- return true if text.start_with?(' ') || text.end_with?(' ')
56
-
57
- text.match?(/[^\n ] +[^ ]/)
55
+ text.match?(/
56
+ # Leading space
57
+ \A[[:blank:]]
58
+ |
59
+ # Trailing space
60
+ [[:blank:]]\z
61
+ |
62
+ # Two or more consecutive spaces, except if they are leading spaces
63
+ [^[[:space:]]][[:blank:]]{2,}[^[[:blank:]]]
64
+ /x)
58
65
  end
59
66
 
60
67
  # @param text [String]
61
68
  def strip_excessive_whitespace(text)
62
- text.strip.gsub(/ +/, ' ')
69
+ text
70
+ .gsub(/[[:blank:]]{2,}/, ' ')
71
+ .gsub(/\A[[:blank:]]|[[:blank:]]\z/, '')
63
72
  end
64
73
 
65
74
  # @param node [RuboCop::AST::Node]
@@ -74,7 +83,7 @@ module RuboCop
74
83
  end
75
84
 
76
85
  def docstring(node)
77
- expr = node.loc.expression
86
+ expr = node.source_range
78
87
 
79
88
  Parser::Source::Range.new(
80
89
  expr.source_buffer,