rubocop-rspec 2.12.1 → 2.13.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -86
  3. data/config/default.yml +44 -6
  4. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +8 -9
  5. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +8 -9
  6. data/lib/rubocop/cop/rspec/any_instance.rb +1 -0
  7. data/lib/rubocop/cop/rspec/around_block.rb +26 -3
  8. data/lib/rubocop/cop/rspec/be.rb +0 -1
  9. data/lib/rubocop/cop/rspec/be_eq.rb +0 -1
  10. data/lib/rubocop/cop/rspec/be_eql.rb +0 -1
  11. data/lib/rubocop/cop/rspec/before_after_all.rb +1 -0
  12. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +9 -3
  13. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -1
  14. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +86 -0
  15. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +91 -10
  16. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +0 -1
  17. data/lib/rubocop/cop/rspec/change_by_zero.rb +60 -5
  18. data/lib/rubocop/cop/rspec/class_check.rb +101 -0
  19. data/lib/rubocop/cop/rspec/context_method.rb +2 -1
  20. data/lib/rubocop/cop/rspec/context_wording.rb +49 -18
  21. data/lib/rubocop/cop/rspec/describe_class.rb +1 -1
  22. data/lib/rubocop/cop/rspec/describe_method.rb +1 -0
  23. data/lib/rubocop/cop/rspec/described_class.rb +4 -14
  24. data/lib/rubocop/cop/rspec/dialect.rb +1 -0
  25. data/lib/rubocop/cop/rspec/empty_example_group.rb +19 -4
  26. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -1
  27. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +4 -9
  28. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  29. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +2 -1
  30. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +32 -2
  31. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -1
  32. data/lib/rubocop/cop/rspec/example_length.rb +2 -1
  33. data/lib/rubocop/cop/rspec/example_without_description.rb +2 -1
  34. data/lib/rubocop/cop/rspec/example_wording.rb +2 -1
  35. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -0
  36. data/lib/rubocop/cop/rspec/expect_actual.rb +3 -0
  37. data/lib/rubocop/cop/rspec/expect_change.rb +1 -1
  38. data/lib/rubocop/cop/rspec/expect_in_hook.rb +4 -1
  39. data/lib/rubocop/cop/rspec/expect_output.rb +1 -0
  40. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +2 -1
  41. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +26 -12
  42. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +1 -0
  43. data/lib/rubocop/cop/rspec/file_path.rb +6 -3
  44. data/lib/rubocop/cop/rspec/focus.rb +18 -0
  45. data/lib/rubocop/cop/rspec/hook_argument.rb +7 -2
  46. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +10 -9
  47. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +0 -1
  48. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -0
  49. data/lib/rubocop/cop/rspec/implicit_expect.rb +0 -2
  50. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  51. data/lib/rubocop/cop/rspec/instance_variable.rb +0 -1
  52. data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -0
  53. data/lib/rubocop/cop/rspec/iterated_expectation.rb +16 -0
  54. data/lib/rubocop/cop/rspec/leading_subject.rb +15 -15
  55. data/lib/rubocop/cop/rspec/let_before_examples.rb +7 -8
  56. data/lib/rubocop/cop/rspec/let_setup.rb +4 -4
  57. data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
  58. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +2 -1
  59. data/lib/rubocop/cop/rspec/mixin/css_selector.rb +99 -0
  60. data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
  61. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -0
  62. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -5
  63. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -3
  64. data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -2
  65. data/lib/rubocop/cop/rspec/named_subject.rb +2 -1
  66. data/lib/rubocop/cop/rspec/nested_groups.rb +45 -25
  67. data/lib/rubocop/cop/rspec/no_expectation_example.rb +64 -0
  68. data/lib/rubocop/cop/rspec/not_to_not.rb +1 -2
  69. data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
  70. data/lib/rubocop/cop/rspec/pending.rb +1 -0
  71. data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -1
  72. data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -2
  73. data/lib/rubocop/cop/rspec/receive_counts.rb +14 -15
  74. data/lib/rubocop/cop/rspec/receive_never.rb +4 -5
  75. data/lib/rubocop/cop/rspec/repeated_description.rb +25 -26
  76. data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
  77. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +28 -29
  78. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +28 -29
  79. data/lib/rubocop/cop/rspec/repeated_include_example.rb +32 -33
  80. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  81. data/lib/rubocop/cop/rspec/scattered_let.rb +1 -5
  82. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  83. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  84. data/lib/rubocop/cop/rspec/stubbed_mock.rb +0 -1
  85. data/lib/rubocop/cop/rspec/subject_declaration.rb +0 -1
  86. data/lib/rubocop/cop/rspec/subject_stub.rb +2 -2
  87. data/lib/rubocop/cop/rspec/unspecified_exception.rb +15 -15
  88. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -0
  89. data/lib/rubocop/cop/rspec/variable_name.rb +6 -7
  90. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -0
  91. data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
  92. data/lib/rubocop/cop/rspec/yield.rb +2 -1
  93. data/lib/rubocop/cop/rspec_cops.rb +3 -0
  94. data/lib/rubocop/rspec/config_formatter.rb +14 -3
  95. data/lib/rubocop/rspec/inject.rb +1 -3
  96. data/lib/rubocop/rspec/language/node_pattern.rb +4 -0
  97. data/lib/rubocop/rspec/language.rb +6 -1
  98. data/lib/rubocop/rspec/version.rb +1 -1
  99. data/lib/rubocop/rspec/wording.rb +2 -2
  100. data/lib/rubocop/rspec.rb +14 -0
  101. data/lib/rubocop-rspec.rb +3 -0
  102. metadata +10 -88
@@ -12,19 +12,21 @@ module RuboCop
12
12
  # expect(page).to have_selector('button')
13
13
  # expect(page).to have_no_selector('button.cls')
14
14
  # expect(page).to have_css('button')
15
- # expect(page).to have_no_css('a.cls', exact_text: 'foo')
15
+ # expect(page).to have_no_css('a.cls', href: 'http://example.com')
16
16
  # expect(page).to have_css('table.cls')
17
17
  # expect(page).to have_css('select')
18
+ # expect(page).to have_css('input', exact_text: 'foo')
18
19
  #
19
20
  # # good
20
21
  # expect(page).to have_button
21
22
  # expect(page).to have_no_button(class: 'cls')
22
23
  # expect(page).to have_button
23
- # expect(page).to have_no_link('foo', class: 'cls')
24
+ # expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
24
25
  # expect(page).to have_table(class: 'cls')
25
26
  # expect(page).to have_select
27
+ # expect(page).to have_field('foo')
26
28
  #
27
- class SpecificMatcher < Base
29
+ class SpecificMatcher < Base # rubocop:disable Metrics/ClassLength
28
30
  MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
29
31
  RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
30
32
  have_no_css].freeze
@@ -32,20 +34,57 @@ module RuboCop
32
34
  'button' => 'button',
33
35
  'a' => 'link',
34
36
  'table' => 'table',
35
- 'select' => 'select'
37
+ 'select' => 'select',
38
+ 'input' => 'field'
36
39
  }.freeze
40
+ SPECIFIC_MATCHER_OPTIONS = {
41
+ 'button' => (
42
+ CssSelector::COMMON_OPTIONS + %w[disabled name value title type]
43
+ ).freeze,
44
+ 'link' => (
45
+ CssSelector::COMMON_OPTIONS + %w[href alt title download]
46
+ ).freeze,
47
+ 'table' => (
48
+ CssSelector::COMMON_OPTIONS + %w[
49
+ caption with_cols cols with_rows rows
50
+ ]
51
+ ).freeze,
52
+ 'select' => (
53
+ CssSelector::COMMON_OPTIONS + %w[
54
+ disabled name placeholder options enabled_options
55
+ disabled_options selected with_selected multiple with_options
56
+ ]
57
+ ).freeze,
58
+ 'field' => (
59
+ CssSelector::COMMON_OPTIONS + %w[
60
+ checked unchecked disabled valid name placeholder
61
+ validation_message readonly with type multiple
62
+ ]
63
+ ).freeze
64
+ }.freeze
65
+ SPECIFIC_MATCHER_PSEUDO_CLASSES = %w[
66
+ not() disabled enabled checked unchecked
67
+ ].freeze
37
68
 
38
69
  # @!method first_argument(node)
39
70
  def_node_matcher :first_argument, <<-PATTERN
40
71
  (send nil? _ (str $_) ... )
41
72
  PATTERN
42
73
 
74
+ # @!method option?(node)
75
+ def_node_search :option?, <<-PATTERN
76
+ (pair (sym %) _)
77
+ PATTERN
78
+
43
79
  def on_send(node)
44
- return unless (arg = first_argument(node))
45
- return unless (matcher = specific_matcher(arg))
46
- return if acceptable_pattern?(arg)
80
+ first_argument(node) do |arg|
81
+ next unless (matcher = specific_matcher(arg))
82
+ next if CssSelector.multiple_selectors?(arg)
83
+ next unless specific_matcher_option?(node, arg, matcher)
84
+ next unless specific_matcher_pseudo_classes?(arg)
47
85
 
48
- add_offense(node, message: message(node, matcher))
86
+ add_offense(node, message: message(node, matcher))
87
+ end
49
88
  end
50
89
 
51
90
  private
@@ -55,8 +94,50 @@ module RuboCop
55
94
  SPECIFIC_MATCHER[splitted_arg]
56
95
  end
57
96
 
58
- def acceptable_pattern?(arg)
59
- arg.match?(/\[.+=\w+\]/) || arg.match?(/[ >,+]/)
97
+ def specific_matcher_option?(node, arg, matcher)
98
+ attrs = CssSelector.attributes(arg).keys
99
+ return true if attrs.empty?
100
+ return false unless replaceable_matcher?(node, matcher, attrs)
101
+
102
+ attrs.all? do |attr|
103
+ SPECIFIC_MATCHER_OPTIONS.fetch(matcher, []).include?(attr)
104
+ end
105
+ end
106
+
107
+ def specific_matcher_pseudo_classes?(arg)
108
+ CssSelector.pseudo_classes(arg).all? do |pseudo_class|
109
+ replaceable_pseudo_class?(pseudo_class, arg)
110
+ end
111
+ end
112
+
113
+ def replaceable_pseudo_class?(pseudo_class, arg)
114
+ unless SPECIFIC_MATCHER_PSEUDO_CLASSES.include?(pseudo_class)
115
+ return false
116
+ end
117
+
118
+ case pseudo_class
119
+ when 'not()' then replaceable_pseudo_class_not?(arg)
120
+ else true
121
+ end
122
+ end
123
+
124
+ def replaceable_pseudo_class_not?(arg)
125
+ arg.scan(/not\(.*?\)/).all? do |not_arg|
126
+ CssSelector.attributes(not_arg).values.all? do |v|
127
+ v.is_a?(TrueClass) || v.is_a?(FalseClass)
128
+ end
129
+ end
130
+ end
131
+
132
+ def replaceable_matcher?(node, matcher, attrs)
133
+ case matcher
134
+ when 'link' then replaceable_to_have_link?(node, attrs)
135
+ else true
136
+ end
137
+ end
138
+
139
+ def replaceable_to_have_link?(node, attrs)
140
+ option?(node, :href) || attrs.include?('href')
60
141
  end
61
142
 
62
143
  def message(node, matcher)
@@ -16,7 +16,6 @@ module RuboCop
16
16
  # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
17
17
  #
18
18
  # @example
19
- #
20
19
  # # bad
21
20
  # expect(page).to have_selector('.foo', visible: false)
22
21
  # expect(page).to have_css('.foo', visible: true)
@@ -5,12 +5,20 @@ module RuboCop
5
5
  module RSpec
6
6
  # Prefer negated matchers over `to change.by(0)`.
7
7
  #
8
- # @example
8
+ # In the case of composite expectations, cop suggest using the
9
+ # negation matchers of `RSpec::Matchers#change`.
10
+ #
11
+ # By default the cop does not support autocorrect of
12
+ # compound expectations, but if you set the
13
+ # negated matcher for `change`, e.g. `not_change` with
14
+ # the `NegatedMatcher` option, the cop will perform the autocorrection.
15
+ #
16
+ # @example NegatedMatcher: ~ (default)
9
17
  # # bad
10
18
  # expect { run }.to change(Foo, :bar).by(0)
11
19
  # expect { run }.to change { Foo.bar }.by(0)
12
20
  #
13
- # # bad - compound expectations
21
+ # # bad - compound expectations (does not support autocorrection)
14
22
  # expect { run }
15
23
  # .to change(Foo, :bar).by(0)
16
24
  # .and change(Foo, :baz).by(0)
@@ -31,10 +39,28 @@ module RuboCop
31
39
  # .to not_change { Foo.bar }
32
40
  # .and not_change { Foo.baz }
33
41
  #
42
+ # @example NegatedMatcher: not_change
43
+ # # bad (support autocorrection to good case)
44
+ # expect { run }
45
+ # .to change(Foo, :bar).by(0)
46
+ # .and change(Foo, :baz).by(0)
47
+ # expect { run }
48
+ # .to change { Foo.bar }.by(0)
49
+ # .and change { Foo.baz }.by(0)
50
+ #
51
+ # # good
52
+ # define_negated_matcher :not_change, :change
53
+ # expect { run }
54
+ # .to not_change(Foo, :bar)
55
+ # .and not_change(Foo, :baz)
56
+ # expect { run }
57
+ # .to not_change { Foo.bar }
58
+ # .and not_change { Foo.baz }
59
+ #
34
60
  class ChangeByZero < Base
35
61
  extend AutoCorrector
36
62
  MSG = 'Prefer `not_to change` over `to change.by(0)`.'
37
- MSG_COMPOUND = 'Prefer negated matchers with compound expectations ' \
63
+ MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
38
64
  'over `change.by(0)`.'
39
65
  RESTRICT_ON_SEND = %i[change].freeze
40
66
 
@@ -55,6 +81,11 @@ module RuboCop
55
81
  (int 0))
56
82
  PATTERN
57
83
 
84
+ # @!method change_nodes(node)
85
+ def_node_search :change_nodes, <<-PATTERN
86
+ $(send nil? :change ...)
87
+ PATTERN
88
+
58
89
  def on_send(node)
59
90
  expect_change_with_arguments(node.parent) do
60
91
  check_offense(node.parent)
@@ -70,7 +101,9 @@ module RuboCop
70
101
  def check_offense(node)
71
102
  expression = node.loc.expression
72
103
  if compound_expectations?(node)
73
- add_offense(expression, message: MSG_COMPOUND)
104
+ add_offense(expression, message: message_compound) do |corrector|
105
+ autocorrect_compound(corrector, node)
106
+ end
74
107
  else
75
108
  add_offense(expression) do |corrector|
76
109
  autocorrect(corrector, node)
@@ -79,7 +112,7 @@ module RuboCop
79
112
  end
80
113
 
81
114
  def compound_expectations?(node)
82
- %i[and or].include?(node.parent.method_name)
115
+ %i[and or & |].include?(node.parent.method_name)
83
116
  end
84
117
 
85
118
  def autocorrect(corrector, node)
@@ -87,6 +120,28 @@ module RuboCop
87
120
  range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
88
121
  corrector.remove(range)
89
122
  end
123
+
124
+ def autocorrect_compound(corrector, node)
125
+ return unless negated_matcher
126
+
127
+ change_nodes(node) do |change_node|
128
+ corrector.replace(change_node.loc.selector, negated_matcher)
129
+ range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
130
+ corrector.remove(range)
131
+ end
132
+ end
133
+
134
+ def negated_matcher
135
+ cop_config['NegatedMatcher']
136
+ end
137
+
138
+ def message_compound
139
+ format(MSG_COMPOUND, preferred: preferred_method)
140
+ end
141
+
142
+ def preferred_method
143
+ negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
144
+ end
90
145
  end
91
146
  end
92
147
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Enforces consistent use of `be_a` or `be_kind_of`.
7
+ #
8
+ # @example EnforcedStyle: be_a (default)
9
+ # # bad
10
+ # expect(object).to be_kind_of(String)
11
+ # expect(object).to be_a_kind_of(String)
12
+ #
13
+ # # good
14
+ # expect(object).to be_a(String)
15
+ # expect(object).to be_an(String)
16
+ #
17
+ # @example EnforcedStyle: be_kind_of
18
+ # # bad
19
+ # expect(object).to be_a(String)
20
+ # expect(object).to be_an(String)
21
+ #
22
+ # # good
23
+ # expect(object).to be_kind_of(String)
24
+ # expect(object).to be_a_kind_of(String)
25
+ #
26
+ class ClassCheck < Base
27
+ extend AutoCorrector
28
+ include ConfigurableEnforcedStyle
29
+
30
+ MSG = 'Prefer `%<preferred>s` over `%<current>s`.'
31
+
32
+ METHOD_NAMES_FOR_BE_A = ::Set[
33
+ :be_a,
34
+ :be_an
35
+ ].freeze
36
+
37
+ METHOD_NAMES_FOR_KIND_OF = ::Set[
38
+ :be_a_kind_of,
39
+ :be_kind_of
40
+ ].freeze
41
+
42
+ PREFERRED_METHOD_NAME_BY_STYLE = {
43
+ be_a: :be_a,
44
+ be_kind_of: :be_kind_of
45
+ }.freeze
46
+
47
+ RESTRICT_ON_SEND = %i[
48
+ be_a
49
+ be_a_kind_of
50
+ be_an
51
+ be_kind_of
52
+ ].freeze
53
+
54
+ def on_send(node)
55
+ return unless offending?(node)
56
+
57
+ add_offense(
58
+ node.loc.selector,
59
+ message: format_message(node)
60
+ ) do |corrector|
61
+ autocorrect(corrector, node)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def autocorrect(corrector, node)
68
+ corrector.replace(node.loc.selector, preferred_method_name)
69
+ end
70
+
71
+ def format_message(node)
72
+ format(
73
+ MSG,
74
+ current: node.method_name,
75
+ preferred: preferred_method_name
76
+ )
77
+ end
78
+
79
+ def offending?(node)
80
+ !node.receiver && !preferred_method_name?(node.method_name)
81
+ end
82
+
83
+ def preferred_method_name?(method_name)
84
+ preferred_method_names.include?(method_name)
85
+ end
86
+
87
+ def preferred_method_name
88
+ PREFERRED_METHOD_NAME_BY_STYLE[style]
89
+ end
90
+
91
+ def preferred_method_names
92
+ if style == :be_a
93
+ METHOD_NAMES_FOR_BE_A
94
+ else
95
+ METHOD_NAMES_FOR_KIND_OF
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -23,6 +23,7 @@ module RuboCop
23
23
  # describe '.foo_bar' do
24
24
  # # ...
25
25
  # end
26
+ #
26
27
  class ContextMethod < Base
27
28
  extend AutoCorrector
28
29
 
@@ -33,7 +34,7 @@ module RuboCop
33
34
  (block (send #rspec? :context $(str #method_name?) ...) ...)
34
35
  PATTERN
35
36
 
36
- def on_block(node)
37
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
37
38
  context_method(node) do |context|
38
39
  add_offense(context) do |corrector|
39
40
  corrector.replace(node.send_node.loc.selector, 'describe')
@@ -14,7 +14,6 @@ module RuboCop
14
14
  # @see http://www.betterspecs.org/#contexts
15
15
  #
16
16
  # @example `Prefixes` configuration
17
- #
18
17
  # # .rubocop.yml
19
18
  # # RSpec/ContextWording:
20
19
  # # Prefixes:
@@ -35,41 +34,73 @@ module RuboCop
35
34
  # context 'when the display name is not present' do
36
35
  # # ...
37
36
  # end
37
+ #
38
+ # This cop can be customized allowed context description pattern
39
+ # with `AllowedPatterns`. By default, there are no checking by pattern.
40
+ #
41
+ # @example `AllowedPatterns` configuration
42
+ #
43
+ # # .rubocop.yml
44
+ # # RSpec/ContextWording:
45
+ # # AllowedPatterns:
46
+ # # - /とき$/
47
+ #
48
+ # @example
49
+ # # bad
50
+ # context '条件を満たす' do
51
+ # # ...
52
+ # end
53
+ #
54
+ # # good
55
+ # context '条件を満たすとき' do
56
+ # # ...
57
+ # end
58
+ #
38
59
  class ContextWording < Base
39
- MSG = 'Start context description with %<prefixes>s.'
60
+ include AllowedPattern
61
+
62
+ MSG = 'Context description should match %<patterns>s.'
40
63
 
41
64
  # @!method context_wording(node)
42
65
  def_node_matcher :context_wording, <<-PATTERN
43
- (block (send #rspec? { :context :shared_context } $(str #bad_prefix?) ...) ...)
66
+ (block (send #rspec? { :context :shared_context } $(str $_) ...) ...)
44
67
  PATTERN
45
68
 
46
- def on_block(node)
47
- context_wording(node) do |context|
48
- add_offense(context,
49
- message: format(MSG, prefixes: joined_prefixes))
69
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
70
+ context_wording(node) do |context, description|
71
+ if bad_pattern?(description)
72
+ message = format(MSG, patterns: expect_patterns)
73
+ add_offense(context, message: message)
74
+ end
50
75
  end
51
76
  end
52
77
 
53
78
  private
54
79
 
55
- def bad_prefix?(description)
56
- !prefix_regex.match?(description)
80
+ def allowed_patterns
81
+ super + prefix_regexes
57
82
  end
58
83
 
59
- def joined_prefixes
60
- quoted = prefixes.map { |prefix| "'#{prefix}'" }
61
- return quoted.first if quoted.size == 1
84
+ def prefix_regexes
85
+ @prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ }
86
+ end
87
+
88
+ def bad_pattern?(description)
89
+ return false if allowed_patterns.empty?
62
90
 
63
- quoted << "or #{quoted.pop}"
64
- quoted.join(', ')
91
+ !matches_allowed_pattern?(description)
65
92
  end
66
93
 
67
- def prefixes
68
- cop_config['Prefixes'] || []
94
+ def expect_patterns
95
+ inspected = allowed_patterns.map(&:inspect)
96
+ return inspected.first if inspected.size == 1
97
+
98
+ inspected << "or #{inspected.pop}"
99
+ inspected.join(', ')
69
100
  end
70
101
 
71
- def prefix_regex
72
- /^#{Regexp.union(prefixes)}\b/
102
+ def prefixes
103
+ Array(cop_config.fetch('Prefixes', []))
73
104
  end
74
105
  end
75
106
  end
@@ -10,7 +10,6 @@ module RuboCop
10
10
  # Ignores Rails and Aruba `type` metadata by default.
11
11
  #
12
12
  # @example `IgnoredMetadata` configuration
13
- #
14
13
  # # .rubocop.yml
15
14
  # # RSpec/DescribeClass:
16
15
  # # IgnoredMetadata:
@@ -34,6 +33,7 @@ module RuboCop
34
33
  #
35
34
  # describe "A feature example", type: :feature do
36
35
  # end
36
+ #
37
37
  class DescribeClass < Base
38
38
  include TopLevelGroup
39
39
 
@@ -16,6 +16,7 @@ module RuboCop
16
16
  #
17
17
  # describe MyClass, '.my_class_method' do
18
18
  # end
19
+ #
19
20
  class DescribeMethod < Base
20
21
  include TopLevelGroup
21
22
 
@@ -57,6 +57,7 @@ module RuboCop
57
57
  class DescribedClass < Base
58
58
  extend AutoCorrector
59
59
  include ConfigurableEnforcedStyle
60
+ include Namespace
60
61
 
61
62
  DESCRIBED_CLASS = 'described_class'
62
63
  MSG = 'Use `%<replacement>s` instead of `%<src>s`.'
@@ -81,7 +82,7 @@ module RuboCop
81
82
  def_node_search :contains_described_class?,
82
83
  '(send nil? :described_class)'
83
84
 
84
- def on_block(node)
85
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
85
86
  # In case the explicit style is used, we need to remember what's
86
87
  # being described.
87
88
  @described_class, body = described_constant(node)
@@ -160,7 +161,8 @@ module RuboCop
160
161
  end
161
162
 
162
163
  def full_const_name(node)
163
- collapse_namespace(namespace(node), const_name(node))
164
+ symbolized_namespace = namespace(node).map(&:to_sym)
165
+ collapse_namespace(symbolized_namespace, const_name(node))
164
166
  end
165
167
 
166
168
  # @param namespace [Array<Symbol>]
@@ -200,18 +202,6 @@ module RuboCop
200
202
  [nil, name]
201
203
  end
202
204
  end
203
-
204
- # @param node [RuboCop::AST::Node]
205
- # @return [Array<Symbol>]
206
- # @example
207
- # namespace(node) # => [:A, :B, :C]
208
- def namespace(node)
209
- node
210
- .each_ancestor(:class, :module)
211
- .reverse_each
212
- .flat_map { |ancestor| ancestor.defined_module_name.split('::') }
213
- .map(&:to_sym)
214
- end
215
205
  end
216
206
  end
217
207
  end
@@ -41,6 +41,7 @@ module RuboCop
41
41
  # describe 'display name presence' do
42
42
  # # ...
43
43
  # end
44
+ #
44
45
  class Dialect < Base
45
46
  extend AutoCorrector
46
47
  include MethodPreference
@@ -6,7 +6,6 @@ module RuboCop
6
6
  # Checks if an example group does not include any tests.
7
7
  #
8
8
  # @example usage
9
- #
10
9
  # # bad
11
10
  # describe Bacon do
12
11
  # let(:bacon) { Bacon.new(chunkiness) }
@@ -35,7 +34,12 @@ module RuboCop
35
34
  # describe Bacon do
36
35
  # pending 'will add tests later'
37
36
  # end
37
+ #
38
38
  class EmptyExampleGroup < Base
39
+ extend AutoCorrector
40
+
41
+ include RangeHelp
42
+
39
43
  MSG = 'Empty example group detected.'
40
44
 
41
45
  # @!method example_group_body(node)
@@ -119,7 +123,7 @@ module RuboCop
119
123
  # describe { it { i_run_as_well } }
120
124
  #
121
125
  # @example source that does not match
122
- # before { it { whatever here wont run anyway } }
126
+ # before { it { whatever here won't run anyway } }
123
127
  #
124
128
  # @param node [RuboCop::AST::Node]
125
129
  # @return [Array<RuboCop::AST::Node>] matching nodes
@@ -130,12 +134,16 @@ module RuboCop
130
134
  }
131
135
  PATTERN
132
136
 
133
- def on_block(node)
137
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
134
138
  return if node.each_ancestor(:def, :defs).any?
135
139
  return if node.each_ancestor(:block).any? { |block| example?(block) }
136
140
 
137
141
  example_group_body(node) do |body|
138
- add_offense(node.send_node) if offensive?(body)
142
+ next unless offensive?(body)
143
+
144
+ add_offense(node.send_node) do |corrector|
145
+ corrector.remove(removed_range(node))
146
+ end
139
147
  end
140
148
  end
141
149
 
@@ -163,6 +171,13 @@ module RuboCop
163
171
  def examples_in_branches?(condition_node)
164
172
  condition_node.branches.any? { |branch| examples?(branch) }
165
173
  end
174
+
175
+ def removed_range(node)
176
+ range_by_whole_lines(
177
+ node.location.expression,
178
+ include_final_newline: true
179
+ )
180
+ end
166
181
  end
167
182
  end
168
183
  end
@@ -22,6 +22,7 @@ module RuboCop
22
22
  # create_feed
23
23
  # end
24
24
  # after(:all) { cleanup_feed }
25
+ #
25
26
  class EmptyHook < Base
26
27
  extend AutoCorrector
27
28
  include RuboCop::Cop::RangeHelp
@@ -33,7 +34,7 @@ module RuboCop
33
34
  (block $#{send_pattern('#Hooks.all')} _ nil?)
34
35
  PATTERN
35
36
 
36
- def on_block(node)
37
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
37
38
  empty_hook?(node) do |hook|
38
39
  add_offense(hook) do |corrector|
39
40
  corrector.remove(
@@ -30,7 +30,6 @@ module RuboCop
30
30
  # end
31
31
  #
32
32
  # @example with AllowConsecutiveOneLiners configuration
33
- #
34
33
  # # rubocop.yml
35
34
  # # RSpec/EmptyLineAfterExample:
36
35
  # # AllowConsecutiveOneLiners: false
@@ -47,7 +46,7 @@ module RuboCop
47
46
 
48
47
  MSG = 'Add an empty line after `%<example>s`.'
49
48
 
50
- def on_block(node)
49
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
51
50
  return unless example?(node)
52
51
  return if allowed_one_liner?(node)
53
52
 
@@ -65,19 +64,15 @@ module RuboCop
65
64
  end
66
65
 
67
66
  def consecutive_one_liner?(node)
68
- node.line_count == 1 && next_one_line_example?(node)
67
+ node.single_line? && next_one_line_example?(node)
69
68
  end
70
69
 
71
70
  def next_one_line_example?(node)
72
- next_sibling = next_sibling(node)
71
+ next_sibling = node.right_sibling
73
72
  return unless next_sibling
74
73
  return unless example?(next_sibling)
75
74
 
76
- next_sibling.line_count == 1
77
- end
78
-
79
- def next_sibling(node)
80
- node.parent.children[node.sibling_index + 1]
75
+ next_sibling.single_line?
81
76
  end
82
77
  end
83
78
  end
@@ -29,7 +29,7 @@ module RuboCop
29
29
 
30
30
  MSG = 'Add an empty line after `%<example_group>s`.'
31
31
 
32
- def on_block(node)
32
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
33
33
  return unless example_group?(node)
34
34
 
35
35
  missing_separating_line_offense(node) do |method|