rubocop-rspec 2.22.0 → 2.27.1

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -9
  3. data/README.md +1 -1
  4. data/config/default.yml +126 -21
  5. data/lib/rubocop/cop/rspec/around_block.rb +3 -3
  6. data/lib/rubocop/cop/rspec/be.rb +1 -1
  7. data/lib/rubocop/cop/rspec/be_empty.rb +1 -0
  8. data/lib/rubocop/cop/rspec/be_eq.rb +1 -1
  9. data/lib/rubocop/cop/rspec/be_eql.rb +1 -1
  10. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  11. data/lib/rubocop/cop/rspec/before_after_all.rb +7 -13
  12. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -2
  13. data/lib/rubocop/cop/rspec/change_by_zero.rb +30 -4
  14. data/lib/rubocop/cop/rspec/context_method.rb +2 -2
  15. data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
  16. data/lib/rubocop/cop/rspec/describe_symbol.rb +1 -1
  17. data/lib/rubocop/cop/rspec/described_class.rb +33 -11
  18. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +1 -1
  19. data/lib/rubocop/cop/rspec/empty_example_group.rb +4 -1
  20. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +2 -2
  21. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  22. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  23. data/lib/rubocop/cop/rspec/example_length.rb +11 -5
  24. data/lib/rubocop/cop/rspec/example_without_description.rb +11 -2
  25. data/lib/rubocop/cop/rspec/example_wording.rb +11 -2
  26. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
  27. data/lib/rubocop/cop/rspec/expect_actual.rb +7 -4
  28. data/lib/rubocop/cop/rspec/expect_change.rb +2 -2
  29. data/lib/rubocop/cop/rspec/expect_output.rb +1 -4
  30. data/lib/rubocop/cop/rspec/file_path.rb +6 -0
  31. data/lib/rubocop/cop/rspec/focus.rb +17 -2
  32. data/lib/rubocop/cop/rspec/hook_argument.rb +2 -2
  33. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -1
  34. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +2 -2
  35. data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
  36. data/lib/rubocop/cop/rspec/implicit_subject.rb +2 -2
  37. data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
  38. data/lib/rubocop/cop/rspec/instance_spy.rb +2 -2
  39. data/lib/rubocop/cop/rspec/instance_variable.rb +3 -3
  40. data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
  41. data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -3
  42. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
  43. data/lib/rubocop/cop/rspec/let_before_examples.rb +5 -1
  44. data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
  45. data/lib/rubocop/cop/rspec/message_expectation.rb +1 -2
  46. data/lib/rubocop/cop/rspec/message_spies.rb +0 -2
  47. data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
  48. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  49. data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
  50. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +2 -2
  51. data/lib/rubocop/cop/rspec/multiple_expectations.rb +12 -7
  52. data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
  53. data/lib/rubocop/cop/rspec/pending.rb +12 -2
  54. data/lib/rubocop/cop/rspec/pending_without_reason.rb +1 -1
  55. data/lib/rubocop/cop/rspec/predicate_matcher.rb +9 -9
  56. data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -1
  57. data/lib/rubocop/cop/rspec/rails/have_http_status.rb +34 -10
  58. data/lib/rubocop/cop/rspec/rails/http_status.rb +29 -18
  59. data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +314 -22
  60. data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
  61. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  62. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  63. data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
  64. data/lib/rubocop/cop/rspec/remove_const.rb +40 -0
  65. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +1 -1
  66. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +2 -2
  67. data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
  68. data/lib/rubocop/cop/rspec/repeated_subject_call.rb +124 -0
  69. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  70. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  71. data/lib/rubocop/cop/rspec/shared_examples.rb +66 -20
  72. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +2 -3
  73. data/lib/rubocop/cop/rspec/sort_metadata.rb +2 -1
  74. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  75. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  76. data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
  77. data/lib/rubocop/cop/rspec/subject_stub.rb +4 -4
  78. data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -2
  79. data/lib/rubocop/cop/rspec/variable_definition.rb +4 -4
  80. data/lib/rubocop/cop/rspec/verified_double_reference.rb +6 -6
  81. data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -2
  82. data/lib/rubocop/cop/rspec/void_expect.rb +4 -3
  83. data/lib/rubocop/cop/rspec_cops.rb +11 -0
  84. data/lib/rubocop/rspec/language.rb +8 -8
  85. data/lib/rubocop/rspec/version.rb +1 -1
  86. data/lib/rubocop/rspec/wording.rb +8 -0
  87. data/lib/rubocop-rspec.rb +1 -0
  88. metadata +20 -8
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that `remove_const` is not used in specs.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it 'does something' do
11
+ # Object.send(:remove_const, :SomeConstant)
12
+ # end
13
+ #
14
+ # before do
15
+ # SomeClass.send(:remove_const, :SomeConstant)
16
+ # end
17
+ #
18
+ class RemoveConst < Base
19
+ include RuboCop::RSpec::Language
20
+ extend RuboCop::RSpec::Language::NodePattern
21
+
22
+ MSG = 'Do not use remove_const in specs. ' \
23
+ 'Consider using e.g. `stub_const`.'
24
+ RESTRICT_ON_SEND = %i[send __send__].freeze
25
+
26
+ # @!method remove_const(node)
27
+ def_node_matcher :remove_const, <<~PATTERN
28
+ (send _ {:send | :__send__} (sym :remove_const) _)
29
+ PATTERN
30
+
31
+ # Check for offenses
32
+ def on_send(node)
33
+ remove_const(node) do
34
+ add_offense(node)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
49
49
 
50
50
  # @!method several_example_groups?(node)
51
- def_node_matcher :several_example_groups?, <<-PATTERN
51
+ def_node_matcher :several_example_groups?, <<~PATTERN
52
52
  (begin <#example_group_with_body? #example_group_with_body? ...>)
53
53
  PATTERN
54
54
 
@@ -48,12 +48,12 @@ module RuboCop
48
48
  MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
49
49
 
50
50
  # @!method several_example_groups?(node)
51
- def_node_matcher :several_example_groups?, <<-PATTERN
51
+ def_node_matcher :several_example_groups?, <<~PATTERN
52
52
  (begin <#example_group? #example_group? ...>)
53
53
  PATTERN
54
54
 
55
55
  # @!method doc_string_and_metadata(node)
56
- def_node_matcher :doc_string_and_metadata, <<-PATTERN
56
+ def_node_matcher :doc_string_and_metadata, <<~PATTERN
57
57
  (block (send _ _ $_ $...) ...)
58
58
  PATTERN
59
59
 
@@ -50,7 +50,7 @@ module RuboCop
50
50
  'on line(s) %<repeat>s'
51
51
 
52
52
  # @!method several_include_examples?(node)
53
- def_node_matcher :several_include_examples?, <<-PATTERN
53
+ def_node_matcher :several_include_examples?, <<~PATTERN
54
54
  (begin <#include_examples? #include_examples? ...>)
55
55
  PATTERN
56
56
 
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for repeated calls to subject missing that it is memoized.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it do
11
+ # subject
12
+ # expect { subject }.to not_change { A.count }
13
+ # end
14
+ #
15
+ # it do
16
+ # expect { subject }.to change { A.count }
17
+ # expect { subject }.to not_change { A.count }
18
+ # end
19
+ #
20
+ # # good
21
+ # it do
22
+ # expect { my_method }.to change { A.count }
23
+ # expect { my_method }.to not_change { A.count }
24
+ # end
25
+ #
26
+ # # also good
27
+ # it do
28
+ # expect { subject.a }.to change { A.count }
29
+ # expect { subject.b }.to not_change { A.count }
30
+ # end
31
+ #
32
+ class RepeatedSubjectCall < Base
33
+ include TopLevelGroup
34
+
35
+ MSG = 'Calls to subject are memoized, this block is misleading'
36
+
37
+ # @!method subject?(node)
38
+ # Find a named or unnamed subject definition
39
+ #
40
+ # @example anonymous subject
41
+ # subject?(parse('subject { foo }').ast) do |name|
42
+ # name # => :subject
43
+ # end
44
+ #
45
+ # @example named subject
46
+ # subject?(parse('subject(:thing) { foo }').ast) do |name|
47
+ # name # => :thing
48
+ # end
49
+ #
50
+ # @param node [RuboCop::AST::Node]
51
+ #
52
+ # @yield [Symbol] subject name
53
+ def_node_matcher :subject?, <<-PATTERN
54
+ (block
55
+ (send nil?
56
+ { #Subjects.all (sym $_) | $#Subjects.all }
57
+ ) args ...)
58
+ PATTERN
59
+
60
+ # @!method subject_calls(node, method_name)
61
+ def_node_search :subject_calls, <<~PATTERN
62
+ (send nil? %)
63
+ PATTERN
64
+
65
+ def on_top_level_group(node)
66
+ @subjects_by_node = detect_subjects_in_scope(node)
67
+
68
+ detect_offenses_in_block(node)
69
+ end
70
+
71
+ private
72
+
73
+ def detect_offense(subject_node)
74
+ return if subject_node.chained?
75
+ return unless (block_node = expect_block(subject_node))
76
+
77
+ add_offense(block_node)
78
+ end
79
+
80
+ def expect_block(node)
81
+ node.each_ancestor(:block).find { |block| block.method?(:expect) }
82
+ end
83
+
84
+ def detect_offenses_in_block(node, subject_names = [])
85
+ subject_names = [*subject_names, *@subjects_by_node[node]]
86
+
87
+ if example?(node)
88
+ return detect_offenses_in_example(node, subject_names)
89
+ end
90
+
91
+ node.each_child_node(:send, :def, :block, :begin) do |child|
92
+ detect_offenses_in_block(child, subject_names)
93
+ end
94
+ end
95
+
96
+ def detect_offenses_in_example(node, subject_names)
97
+ return unless node.body
98
+
99
+ subjects_used = Hash.new(false)
100
+
101
+ subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
102
+ if subjects_used[call.method_name]
103
+ detect_offense(call)
104
+ else
105
+ subjects_used[call.method_name] = true
106
+ end
107
+ end
108
+ end
109
+
110
+ def detect_subjects_in_scope(node)
111
+ node.each_descendant(:block).with_object({}) do |child, h|
112
+ subject?(child) do |name|
113
+ outer_example_group = child.each_ancestor(:block).find do |a|
114
+ example_group?(a)
115
+ end
116
+
117
+ (h[outer_example_group] ||= []) << name
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
49
49
 
50
50
  # @!method and_return_value(node)
51
- def_node_search :and_return_value, <<-PATTERN
51
+ def_node_search :and_return_value, <<~PATTERN
52
52
  $(send _ :and_return $(...))
53
53
  PATTERN
54
54
 
@@ -62,7 +62,7 @@ module RuboCop
62
62
  PATTERN
63
63
 
64
64
  # @!method context?(node)
65
- def_node_search :context?, <<-PATTERN
65
+ def_node_search :context?, <<~PATTERN
66
66
  (send nil?
67
67
  {#Subjects.all #Helpers.all #Includes.context #Hooks.all} ...
68
68
  )
@@ -3,9 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Enforces use of string to titleize shared examples.
6
+ # Checks for consistent style for shared example names.
7
7
  #
8
- # @example
8
+ # Enforces either `string` or `symbol` for shared example names.
9
+ #
10
+ # This cop can be configured using the `EnforcedStyle` option
11
+ #
12
+ # @example `EnforcedStyle: string` (default)
9
13
  # # bad
10
14
  # it_behaves_like :foo_bar_baz
11
15
  # it_should_behave_like :foo_bar_baz
@@ -20,33 +24,66 @@ module RuboCop
20
24
  # shared_examples_for 'foo bar baz'
21
25
  # include_examples 'foo bar baz'
22
26
  #
27
+ # @example `EnforcedStyle: symbol`
28
+ # # bad
29
+ # it_behaves_like 'foo bar baz'
30
+ # it_should_behave_like 'foo bar baz'
31
+ # shared_examples 'foo bar baz'
32
+ # shared_examples_for 'foo bar baz'
33
+ # include_examples 'foo bar baz'
34
+ #
35
+ # # good
36
+ # it_behaves_like :foo_bar_baz
37
+ # it_should_behave_like :foo_bar_baz
38
+ # shared_examples :foo_bar_baz
39
+ # shared_examples_for :foo_bar_baz
40
+ # include_examples :foo_bar_baz
41
+ #
23
42
  class SharedExamples < Base
24
43
  extend AutoCorrector
44
+ include ConfigurableEnforcedStyle
25
45
 
26
46
  # @!method shared_examples(node)
27
47
  def_node_matcher :shared_examples, <<~PATTERN
28
48
  {
29
- (send #rspec? #SharedGroups.all ...)
30
- (send nil? #Includes.all ...)
49
+ (send #rspec? #SharedGroups.all $_ ...)
50
+ (send nil? #Includes.all $_ ...)
31
51
  }
32
52
  PATTERN
33
53
 
34
54
  def on_send(node)
35
- shared_examples(node) do
36
- ast_node = node.first_argument
37
- next unless ast_node&.sym_type?
55
+ shared_examples(node) do |ast_node|
56
+ next unless offense?(ast_node)
38
57
 
39
- checker = Checker.new(ast_node)
40
- add_offense(checker.node, message: checker.message) do |corrector|
41
- corrector.replace(checker.node, checker.preferred_style)
58
+ checker = new_checker(ast_node)
59
+ add_offense(ast_node, message: checker.message) do |corrector|
60
+ corrector.replace(ast_node, checker.preferred_style)
42
61
  end
43
62
  end
44
63
  end
45
64
 
65
+ private
66
+
67
+ def offense?(ast_node)
68
+ if style == :symbol
69
+ ast_node.str_type?
70
+ else # string
71
+ ast_node.sym_type?
72
+ end
73
+ end
74
+
75
+ def new_checker(ast_node)
76
+ if style == :symbol
77
+ SymbolChecker.new(ast_node)
78
+ else # string
79
+ StringChecker.new(ast_node)
80
+ end
81
+ end
82
+
46
83
  # :nodoc:
47
- class Checker
84
+ class SymbolChecker
48
85
  MSG = 'Prefer %<prefer>s over `%<current>s` ' \
49
- 'to titleize shared examples.'
86
+ 'to symbolize shared examples.'
50
87
 
51
88
  attr_reader :node
52
89
 
@@ -55,22 +92,31 @@ module RuboCop
55
92
  end
56
93
 
57
94
  def message
58
- format(MSG, prefer: preferred_style, current: symbol.inspect)
95
+ format(MSG, prefer: preferred_style, current: node.value.inspect)
59
96
  end
60
97
 
61
98
  def preferred_style
62
- string = symbol.to_s.tr('_', ' ')
63
- wrap_with_single_quotes(string)
99
+ ":#{node.value.to_s.downcase.tr(' ', '_')}"
64
100
  end
101
+ end
102
+
103
+ # :nodoc:
104
+ class StringChecker
105
+ MSG = 'Prefer %<prefer>s over `%<current>s` ' \
106
+ 'to titleize shared examples.'
65
107
 
66
- private
108
+ attr_reader :node
67
109
 
68
- def symbol
69
- node.value
110
+ def initialize(node)
111
+ @node = node
70
112
  end
71
113
 
72
- def wrap_with_single_quotes(string)
73
- "'#{string}'"
114
+ def message
115
+ format(MSG, prefer: preferred_style, current: node.value.inspect)
116
+ end
117
+
118
+ def preferred_style
119
+ "'#{node.value.to_s.tr('_', ' ')}'"
74
120
  end
75
121
  end
76
122
  end
@@ -24,7 +24,7 @@ module RuboCop
24
24
  RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
25
25
 
26
26
  # @!method message_chain(node)
27
- def_node_matcher :message_chain, <<-PATTERN
27
+ def_node_matcher :message_chain, <<~PATTERN
28
28
  (send _ {:receive_message_chain :stub_chain} $_)
29
29
  PATTERN
30
30
 
@@ -81,8 +81,7 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def key_to_arg(node)
84
- key, = *node # rubocop:disable InternalAffairs/NodeDestructuring
85
- node.sym_type? ? ":#{key}" : node.source
84
+ node.sym_type? ? ":#{node.value}" : node.source
86
85
  end
87
86
 
88
87
  def replacement(method)
@@ -23,7 +23,8 @@ module RuboCop
23
23
 
24
24
  MSG = 'Sort metadata alphabetically.'
25
25
 
26
- def on_metadata(symbols, pairs)
26
+ def on_metadata(symbols, hash)
27
+ pairs = hash&.pairs || []
27
28
  return if sorted?(symbols, pairs)
28
29
 
29
30
  crime_scene = crime_scene(symbols, pairs)
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that spec file paths are consistent and well-formed.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # whatever_spec.rb # describe MyClass
11
+ # my_class_spec.rb # describe MyClass, '#method'
12
+ #
13
+ # # good
14
+ # my_class_spec.rb # describe MyClass
15
+ # my_class_method_spec.rb # describe MyClass, '#method'
16
+ # my_class/method_spec.rb # describe MyClass, '#method'
17
+ #
18
+ # @example `CustomTransform: {RuboCop=>rubocop, RSpec=>rspec}` (default)
19
+ # # good
20
+ # rubocop_spec.rb # describe RuboCop
21
+ # rspec_spec.rb # describe RSpec
22
+ #
23
+ # @example `IgnoreMethods: false` (default)
24
+ # # bad
25
+ # my_class_spec.rb # describe MyClass, '#method'
26
+ #
27
+ # @example `IgnoreMethods: true`
28
+ # # good
29
+ # my_class_spec.rb # describe MyClass, '#method'
30
+ #
31
+ # @example `IgnoreMetadata: {type=>routing}` (default)
32
+ # # good
33
+ # whatever_spec.rb # describe MyClass, type: :routing do; end
34
+ #
35
+ class SpecFilePathFormat < Base
36
+ include TopLevelGroup
37
+ include Namespace
38
+ include FileHelp
39
+
40
+ MSG = 'Spec path should end with `%<suffix>s`.'
41
+
42
+ # @!method example_group_arguments(node)
43
+ def_node_matcher :example_group_arguments, <<~PATTERN
44
+ (block $(send #rspec? #ExampleGroups.all $_ $...) ...)
45
+ PATTERN
46
+
47
+ # @!method metadata_key_value(node)
48
+ def_node_search :metadata_key_value, '(pair (sym $_key) (sym $_value))'
49
+
50
+ def on_top_level_example_group(node)
51
+ return unless top_level_groups.one?
52
+
53
+ example_group_arguments(node) do |send_node, class_name, arguments|
54
+ next if !class_name.const_type? || ignore_metadata?(arguments)
55
+
56
+ ensure_correct_file_path(send_node, class_name, arguments)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def ensure_correct_file_path(send_node, class_name, arguments)
63
+ pattern = correct_path_pattern(class_name, arguments)
64
+ return if filename_ends_with?(pattern)
65
+
66
+ # For the suffix shown in the offense message, modify the regular
67
+ # expression pattern to resemble a glob pattern for clearer error
68
+ # messages.
69
+ suffix = pattern.sub('.*', '*').sub('[^/]*', '*').sub('\.', '.')
70
+ add_offense(send_node, message: format(MSG, suffix: suffix))
71
+ end
72
+
73
+ def ignore_metadata?(arguments)
74
+ arguments.any? do |argument|
75
+ metadata_key_value(argument).any? do |key, value|
76
+ ignore_metadata.values_at(key.to_s).include?(value.to_s)
77
+ end
78
+ end
79
+ end
80
+
81
+ def correct_path_pattern(class_name, arguments)
82
+ path = [expected_path(class_name)]
83
+ path << '.*' unless ignore?(arguments.first)
84
+ path << [name_pattern(arguments.first), '[^/]*_spec\.rb']
85
+ path.join
86
+ end
87
+
88
+ def name_pattern(method_name)
89
+ return if ignore?(method_name)
90
+
91
+ method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')
92
+ end
93
+
94
+ def ignore?(method_name)
95
+ !method_name&.str_type? || ignore_methods?
96
+ end
97
+
98
+ def expected_path(constant)
99
+ constants = namespace(constant) + constant.const_name.split('::')
100
+
101
+ File.join(
102
+ constants.map do |name|
103
+ custom_transform.fetch(name) { camel_to_snake_case(name) }
104
+ end
105
+ )
106
+ end
107
+
108
+ def camel_to_snake_case(string)
109
+ string
110
+ .gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
111
+ .gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
112
+ .downcase
113
+ end
114
+
115
+ def custom_transform
116
+ cop_config.fetch('CustomTransform', {})
117
+ end
118
+
119
+ def ignore_methods?
120
+ cop_config['IgnoreMethods']
121
+ end
122
+
123
+ def ignore_metadata
124
+ cop_config.fetch('IgnoreMetadata', {})
125
+ end
126
+
127
+ def filename_ends_with?(pattern)
128
+ expanded_file_path.match?("#{pattern}$")
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that spec file paths suffix are consistent and well-formed.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # my_class/foo_specorb.rb # describe MyClass
11
+ # spec/models/user.rb # describe User
12
+ # spec/models/user_specxrb # describe User
13
+ #
14
+ # # good
15
+ # my_class_spec.rb # describe MyClass
16
+ #
17
+ # # good - shared examples are allowed
18
+ # spec/models/user.rb # shared_examples_for 'foo'
19
+ #
20
+ class SpecFilePathSuffix < Base
21
+ include TopLevelGroup
22
+ include FileHelp
23
+
24
+ MSG = 'Spec path should end with `_spec.rb`.'
25
+
26
+ def on_top_level_example_group(node)
27
+ example_group?(node) do
28
+ add_global_offense(MSG) unless correct_path?
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def correct_path?
35
+ expanded_file_path.end_with?('_spec.rb')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -31,7 +31,7 @@ module RuboCop
31
31
  #
32
32
  # @param node [RuboCop::AST::Node]
33
33
  # @return [Array<RuboCop::AST::Node>] matching nodes
34
- def_node_matcher :message_expectation?, <<-PATTERN
34
+ def_node_matcher :message_expectation?, <<~PATTERN
35
35
  {
36
36
  (send nil? { :receive :receive_message_chain } ...) # receive(:foo)
37
37
  (send (send nil? :receive ...) :with ...) # receive(:foo).with('bar')
@@ -68,7 +68,7 @@ module RuboCop
68
68
  # @param node [RuboCop::AST::Node]
69
69
  #
70
70
  # @yield [Symbol] subject name
71
- def_node_matcher :subject?, <<-PATTERN
71
+ def_node_matcher :subject?, <<~PATTERN
72
72
  (block
73
73
  (send nil?
74
74
  { #Subjects.all (sym $_) | $#Subjects.all }
@@ -77,7 +77,7 @@ module RuboCop
77
77
 
78
78
  # @!method let?(node)
79
79
  # Find a memoized helper
80
- def_node_matcher :let?, <<-PATTERN
80
+ def_node_matcher :let?, <<~PATTERN
81
81
  (block
82
82
  (send nil? :let (sym $_)
83
83
  ) args ...)
@@ -94,7 +94,7 @@ module RuboCop
94
94
  # expect(foo).to receive(:bar).with(1)
95
95
  # expect(foo).to receive(:bar).with(1).and_return(2)
96
96
  #
97
- def_node_matcher :message_expectation?, <<-PATTERN
97
+ def_node_matcher :message_expectation?, <<~PATTERN
98
98
  (send
99
99
  {
100
100
  (send nil? { :expect :allow } (send nil? %))
@@ -106,7 +106,7 @@ module RuboCop
106
106
  PATTERN
107
107
 
108
108
  # @!method message_expectation_matcher?(node)
109
- def_node_search :message_expectation_matcher?, <<-PATTERN
109
+ def_node_search :message_expectation_matcher?, <<~PATTERN
110
110
  (send nil? {
111
111
  :receive :receive_messages :receive_message_chain :have_received
112
112
  } ...)
@@ -35,7 +35,7 @@ module RuboCop
35
35
  RESTRICT_ON_SEND = %i[to].freeze
36
36
 
37
37
  # @!method empty_raise_error_or_exception(node)
38
- def_node_matcher :empty_raise_error_or_exception, <<-PATTERN
38
+ def_node_matcher :empty_raise_error_or_exception, <<~PATTERN
39
39
  (send
40
40
  (block
41
41
  (send nil? :expect) ...)
@@ -57,7 +57,7 @@ module RuboCop
57
57
  end
58
58
 
59
59
  def block_with_args?(node)
60
- return unless node&.block_type?
60
+ return false unless node&.block_type?
61
61
 
62
62
  node.arguments?
63
63
  end
@@ -35,7 +35,7 @@ module RuboCop
35
35
  return unless inside_example_group?(node)
36
36
 
37
37
  variable_definition?(node) do |variable|
38
- next unless style_violation?(variable)
38
+ next unless style_offense?(variable)
39
39
 
40
40
  add_offense(
41
41
  variable,
@@ -59,9 +59,9 @@ module RuboCop
59
59
  end
60
60
  end
61
61
 
62
- def style_violation?(variable)
63
- style == :symbols && string?(variable) ||
64
- style == :strings && symbol?(variable)
62
+ def style_offense?(variable)
63
+ (style == :symbols && string?(variable)) ||
64
+ (style == :strings && symbol?(variable))
65
65
  end
66
66
 
67
67
  def string?(node)
@@ -7,7 +7,7 @@ module RuboCop
7
7
  #
8
8
  # Only investigates references that are one of the supported styles.
9
9
  #
10
- # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
10
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
11
11
  #
12
12
  # This cop can be configured in your configuration using the
13
13
  # `EnforcedStyle` option and supports `--auto-gen-config`.
@@ -79,8 +79,8 @@ module RuboCop
79
79
  expression = class_reference.source_range
80
80
 
81
81
  add_offense(expression, message: message) do |corrector|
82
- violation = class_reference.source
83
- corrector.replace(expression, correct_style(violation))
82
+ offense = class_reference.source
83
+ corrector.replace(expression, correct_style(offense))
84
84
 
85
85
  opposite_style_detected
86
86
  end
@@ -98,11 +98,11 @@ module RuboCop
98
98
  class_reference_style != style
99
99
  end
100
100
 
101
- def correct_style(violation)
101
+ def correct_style(offense)
102
102
  if style == :string
103
- "'#{violation}'"
103
+ "'#{offense}'"
104
104
  else
105
- violation.gsub(/^['"]|['"]$/, '')
105
+ offense.gsub(/^['"]|['"]$/, '')
106
106
  end
107
107
  end
108
108
  end