rubocop-rspec 2.22.0 → 2.27.1

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