rubocop-rspec 2.12.0 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -86
  3. data/config/default.yml +44 -6
  4. data/config/obsoletion.yml +14 -0
  5. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +8 -9
  6. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +8 -9
  7. data/lib/rubocop/cop/rspec/any_instance.rb +1 -0
  8. data/lib/rubocop/cop/rspec/around_block.rb +26 -3
  9. data/lib/rubocop/cop/rspec/be.rb +0 -1
  10. data/lib/rubocop/cop/rspec/be_eq.rb +0 -1
  11. data/lib/rubocop/cop/rspec/be_eql.rb +0 -1
  12. data/lib/rubocop/cop/rspec/before_after_all.rb +1 -0
  13. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +9 -3
  14. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -1
  15. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +86 -0
  16. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +91 -10
  17. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +0 -1
  18. data/lib/rubocop/cop/rspec/change_by_zero.rb +60 -5
  19. data/lib/rubocop/cop/rspec/class_check.rb +101 -0
  20. data/lib/rubocop/cop/rspec/context_method.rb +2 -1
  21. data/lib/rubocop/cop/rspec/context_wording.rb +49 -18
  22. data/lib/rubocop/cop/rspec/describe_class.rb +1 -1
  23. data/lib/rubocop/cop/rspec/describe_method.rb +1 -0
  24. data/lib/rubocop/cop/rspec/described_class.rb +4 -14
  25. data/lib/rubocop/cop/rspec/dialect.rb +1 -0
  26. data/lib/rubocop/cop/rspec/empty_example_group.rb +19 -4
  27. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -1
  28. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +4 -9
  29. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  30. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +2 -1
  31. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +32 -2
  32. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -1
  33. data/lib/rubocop/cop/rspec/example_length.rb +2 -1
  34. data/lib/rubocop/cop/rspec/example_without_description.rb +2 -1
  35. data/lib/rubocop/cop/rspec/example_wording.rb +2 -1
  36. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -0
  37. data/lib/rubocop/cop/rspec/expect_actual.rb +3 -0
  38. data/lib/rubocop/cop/rspec/expect_change.rb +1 -1
  39. data/lib/rubocop/cop/rspec/expect_in_hook.rb +4 -1
  40. data/lib/rubocop/cop/rspec/expect_output.rb +1 -0
  41. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +2 -1
  42. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +26 -12
  43. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +1 -0
  44. data/lib/rubocop/cop/rspec/file_path.rb +6 -3
  45. data/lib/rubocop/cop/rspec/focus.rb +18 -0
  46. data/lib/rubocop/cop/rspec/hook_argument.rb +7 -2
  47. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +10 -9
  48. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +0 -1
  49. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -0
  50. data/lib/rubocop/cop/rspec/implicit_expect.rb +0 -2
  51. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  52. data/lib/rubocop/cop/rspec/instance_variable.rb +0 -1
  53. data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -0
  54. data/lib/rubocop/cop/rspec/iterated_expectation.rb +16 -0
  55. data/lib/rubocop/cop/rspec/leading_subject.rb +15 -15
  56. data/lib/rubocop/cop/rspec/let_before_examples.rb +7 -8
  57. data/lib/rubocop/cop/rspec/let_setup.rb +4 -4
  58. data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
  59. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +2 -1
  60. data/lib/rubocop/cop/rspec/mixin/css_selector.rb +99 -0
  61. data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
  62. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -0
  63. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -5
  64. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -3
  65. data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -2
  66. data/lib/rubocop/cop/rspec/named_subject.rb +2 -1
  67. data/lib/rubocop/cop/rspec/nested_groups.rb +45 -25
  68. data/lib/rubocop/cop/rspec/no_expectation_example.rb +64 -0
  69. data/lib/rubocop/cop/rspec/not_to_not.rb +1 -2
  70. data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
  71. data/lib/rubocop/cop/rspec/pending.rb +1 -0
  72. data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -1
  73. data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -2
  74. data/lib/rubocop/cop/rspec/receive_counts.rb +14 -15
  75. data/lib/rubocop/cop/rspec/receive_never.rb +4 -5
  76. data/lib/rubocop/cop/rspec/repeated_description.rb +25 -26
  77. data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
  78. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +28 -29
  79. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +28 -29
  80. data/lib/rubocop/cop/rspec/repeated_include_example.rb +32 -33
  81. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  82. data/lib/rubocop/cop/rspec/scattered_let.rb +1 -5
  83. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  84. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  85. data/lib/rubocop/cop/rspec/stubbed_mock.rb +0 -1
  86. data/lib/rubocop/cop/rspec/subject_declaration.rb +0 -1
  87. data/lib/rubocop/cop/rspec/subject_stub.rb +2 -2
  88. data/lib/rubocop/cop/rspec/unspecified_exception.rb +15 -15
  89. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -0
  90. data/lib/rubocop/cop/rspec/variable_name.rb +6 -7
  91. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -0
  92. data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
  93. data/lib/rubocop/cop/rspec/yield.rb +2 -1
  94. data/lib/rubocop/cop/rspec_cops.rb +3 -0
  95. data/lib/rubocop/rspec/config_formatter.rb +14 -3
  96. data/lib/rubocop/rspec/inject.rb +1 -3
  97. data/lib/rubocop/rspec/language/node_pattern.rb +4 -0
  98. data/lib/rubocop/rspec/language.rb +6 -1
  99. data/lib/rubocop/rspec/version.rb +1 -1
  100. data/lib/rubocop/rspec/wording.rb +2 -2
  101. data/lib/rubocop/rspec.rb +14 -0
  102. data/lib/rubocop-rspec.rb +3 -0
  103. metadata +11 -88
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Capybara
7
+ # Checks if there is a more specific finder offered by Capybara.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # find('#some-id')
12
+ # find('[visible][id=some-id]')
13
+ #
14
+ # # good
15
+ # find_by_id('some-id')
16
+ # find_by_id('some-id', visible: true)
17
+ #
18
+ class SpecificFinders < Base
19
+ extend AutoCorrector
20
+
21
+ include RangeHelp
22
+
23
+ MSG = 'Prefer `find_by` over `find`.'
24
+ RESTRICT_ON_SEND = %i[find].freeze
25
+
26
+ # @!method find_argument(node)
27
+ def_node_matcher :find_argument, <<~PATTERN
28
+ (send _ :find (str $_) ...)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ find_argument(node) do |arg|
33
+ next if CssSelector.multiple_selectors?(arg)
34
+
35
+ on_attr(node, arg) if attribute?(arg)
36
+ on_id(node, arg) if CssSelector.id?(arg)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def on_attr(node, arg)
43
+ return unless (id = CssSelector.attributes(arg)['id'])
44
+
45
+ register_offense(node, replaced_arguments(arg, id))
46
+ end
47
+
48
+ def on_id(node, arg)
49
+ register_offense(node, "'#{arg.to_s.delete('#')}'")
50
+ end
51
+
52
+ def attribute?(arg)
53
+ CssSelector.attribute?(arg) &&
54
+ CssSelector.common_attributes?(arg)
55
+ end
56
+
57
+ def register_offense(node, arg_replacement)
58
+ add_offense(offense_range(node)) do |corrector|
59
+ corrector.replace(node.loc.selector, 'find_by_id')
60
+ corrector.replace(node.first_argument.loc.expression,
61
+ arg_replacement)
62
+ end
63
+ end
64
+
65
+ def replaced_arguments(arg, id)
66
+ options = to_options(CssSelector.attributes(arg))
67
+ options.empty? ? id : "#{id}, #{options}"
68
+ end
69
+
70
+ def to_options(attrs)
71
+ attrs.each.map do |key, value|
72
+ next if key == 'id'
73
+
74
+ "#{key}: #{value}"
75
+ end.compact.join(', ')
76
+ end
77
+
78
+ def offense_range(node)
79
+ range_between(node.loc.selector.begin_pos,
80
+ node.loc.end.end_pos)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -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+\]/)
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