rubocop-rspec 2.16.0 → 2.24.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +124 -9
  3. data/README.md +3 -3
  4. data/config/default.yml +145 -18
  5. data/config/obsoletion.yml +15 -0
  6. data/lib/rubocop/cop/rspec/be_empty.rb +44 -0
  7. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  8. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +29 -115
  9. data/lib/rubocop/cop/rspec/capybara/match_style.rb +38 -0
  10. data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +23 -96
  11. data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +19 -75
  12. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +14 -83
  13. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +25 -69
  14. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +26 -63
  15. data/lib/rubocop/cop/rspec/change_by_zero.rb +33 -23
  16. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  17. data/lib/rubocop/cop/rspec/context_method.rb +5 -1
  18. data/lib/rubocop/cop/rspec/context_wording.rb +13 -6
  19. data/lib/rubocop/cop/rspec/describe_method.rb +16 -8
  20. data/lib/rubocop/cop/rspec/described_class.rb +2 -1
  21. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +7 -5
  22. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  23. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +2 -2
  24. data/lib/rubocop/cop/rspec/empty_example_group.rb +10 -7
  25. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -2
  26. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  27. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  28. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  29. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  30. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
  31. data/lib/rubocop/cop/rspec/expect_actual.rb +4 -4
  32. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  33. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +25 -118
  34. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +40 -107
  35. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +30 -250
  36. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +19 -46
  37. data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +23 -64
  38. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +45 -79
  39. data/lib/rubocop/cop/rspec/file_path.rb +8 -2
  40. data/lib/rubocop/cop/rspec/focus.rb +19 -5
  41. data/lib/rubocop/cop/rspec/hook_argument.rb +12 -9
  42. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +5 -3
  43. data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
  44. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  45. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
  46. data/lib/rubocop/cop/rspec/let_before_examples.rb +8 -4
  47. data/lib/rubocop/cop/rspec/let_setup.rb +6 -8
  48. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  49. data/lib/rubocop/cop/rspec/metadata_style.rb +197 -0
  50. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +1 -2
  51. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  52. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  53. data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
  54. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +20 -4
  55. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -1
  56. data/lib/rubocop/cop/rspec/named_subject.rb +7 -5
  57. data/lib/rubocop/cop/rspec/no_expectation_example.rb +2 -5
  58. data/lib/rubocop/cop/rspec/overwriting_setup.rb +3 -1
  59. data/lib/rubocop/cop/rspec/pending.rb +23 -13
  60. data/lib/rubocop/cop/rspec/pending_without_reason.rb +72 -36
  61. data/lib/rubocop/cop/rspec/predicate_matcher.rb +49 -40
  62. data/lib/rubocop/cop/rspec/rails/have_http_status.rb +11 -6
  63. data/lib/rubocop/cop/rspec/rails/http_status.rb +107 -34
  64. data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +4 -4
  65. data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +60 -0
  66. data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
  67. data/lib/rubocop/cop/rspec/rails/travel_around.rb +92 -0
  68. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  69. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  70. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  71. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +3 -6
  72. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +3 -6
  73. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -4
  74. data/lib/rubocop/cop/rspec/scattered_setup.rb +23 -6
  75. data/lib/rubocop/cop/rspec/shared_context.rb +12 -13
  76. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -4
  77. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  78. data/lib/rubocop/cop/rspec/sort_metadata.rb +4 -3
  79. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  80. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  81. data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
  82. data/lib/rubocop/cop/rspec/subject_stub.rb +0 -1
  83. data/lib/rubocop/cop/rspec/variable_definition.rb +5 -2
  84. data/lib/rubocop/cop/rspec/variable_name.rb +4 -1
  85. data/lib/rubocop/cop/rspec/verified_double_reference.rb +7 -7
  86. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  87. data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
  88. data/lib/rubocop/cop/rspec_cops.rb +16 -0
  89. data/lib/rubocop/rspec/config_formatter.rb +16 -0
  90. data/lib/rubocop/rspec/example_group.rb +6 -8
  91. data/lib/rubocop/rspec/language/node_pattern.rb +26 -0
  92. data/lib/rubocop/rspec/language.rb +25 -16
  93. data/lib/rubocop/rspec/version.rb +1 -1
  94. data/lib/rubocop-rspec.rb +4 -5
  95. metadata +50 -8
  96. data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +0 -80
  97. data/lib/rubocop/cop/rspec/mixin/css_selector.rb +0 -146
  98. data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
  99. data/lib/rubocop/rspec/factory_bot.rb +0 -64
@@ -4,52 +4,25 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Use string value when setting the class attribute explicitly.
8
- #
9
- # This cop would promote faster tests by lazy-loading of
10
- # application files. Also, this could help you suppress potential bugs
11
- # in combination with external libraries by avoiding a preload of
12
- # application files from the factory files.
13
- #
14
- # @example
15
- # # bad
16
- # factory :foo, class: Foo do
17
- # end
18
- #
19
- # # good
20
- # factory :foo, class: 'Foo' do
21
- # end
22
- #
23
- class FactoryClassName < ::RuboCop::Cop::Base
24
- extend AutoCorrector
25
-
26
- MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
27
- 'constant.'
28
- ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze
29
- RESTRICT_ON_SEND = %i[factory].freeze
30
-
31
- # @!method class_name(node)
32
- def_node_matcher :class_name, <<~PATTERN
33
- (send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>))
34
- PATTERN
35
-
36
- def on_send(node)
37
- class_name(node) do |cn|
38
- next if allowed?(cn.const_name)
39
-
40
- msg = format(MSG, class_name: cn.const_name)
41
- add_offense(cn, message: msg) do |corrector|
42
- corrector.replace(cn, "'#{cn.source}'")
43
- end
44
- end
45
- end
46
-
47
- private
48
-
49
- def allowed?(const_name)
50
- ALLOWED_CONSTANTS.include?(const_name)
51
- end
52
- end
7
+ # @!parse
8
+ # # Use string value when setting the class attribute explicitly.
9
+ # #
10
+ # # This cop would promote faster tests by lazy-loading of
11
+ # # application files. Also, this could help you suppress potential
12
+ # # bugs in combination with external libraries by avoiding a preload
13
+ # # of application files from the factory files.
14
+ # #
15
+ # # @example
16
+ # # # bad
17
+ # # factory :foo, class: Foo do
18
+ # # end
19
+ # #
20
+ # # # good
21
+ # # factory :foo, class: 'Foo' do
22
+ # # end
23
+ # #
24
+ # class FactoryClassName < ::RuboCop::Cop::Base; end
25
+ FactoryClassName = ::RuboCop::Cop::FactoryBot::FactoryClassName
53
26
  end
54
27
  end
55
28
  end
@@ -4,70 +4,29 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Checks for name style for argument of FactoryBot::Syntax::Methods.
8
- #
9
- # @example EnforcedStyle: symbol (default)
10
- # # bad
11
- # create('user')
12
- # build "user", username: "NAME"
13
- #
14
- # # good
15
- # create(:user)
16
- # build :user, username: "NAME"
17
- #
18
- # @example EnforcedStyle: string
19
- # # bad
20
- # create(:user)
21
- # build :user, username: "NAME"
22
- #
23
- # # good
24
- # create('user')
25
- # build "user", username: "NAME"
26
- #
27
- class FactoryNameStyle < ::RuboCop::Cop::Base
28
- extend AutoCorrector
29
- include ConfigurableEnforcedStyle
30
- include RuboCop::RSpec::FactoryBot::Language
31
-
32
- MSG = 'Use %<prefer>s to refer to a factory.'
33
- FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS
34
- RESTRICT_ON_SEND = FACTORY_CALLS
35
-
36
- # @!method factory_call(node)
37
- def_node_matcher :factory_call, <<-PATTERN
38
- (send
39
- {#factory_bot? nil?} %FACTORY_CALLS
40
- ${str sym} ...
41
- )
42
- PATTERN
43
-
44
- def on_send(node)
45
- factory_call(node) do |name|
46
- if offense_for_symbol_style?(name)
47
- register_offense(name, name.value.to_sym.inspect)
48
- elsif offense_for_string_style?(name)
49
- register_offense(name, name.value.to_s.inspect)
50
- end
51
- end
52
- end
53
-
54
- private
55
-
56
- def offense_for_symbol_style?(name)
57
- name.str_type? && style == :symbol
58
- end
59
-
60
- def offense_for_string_style?(name)
61
- name.sym_type? && style == :string
62
- end
63
-
64
- def register_offense(name, prefer)
65
- add_offense(name,
66
- message: format(MSG, prefer: style.to_s)) do |corrector|
67
- corrector.replace(name, prefer)
68
- end
69
- end
70
- end
7
+ # @!parse
8
+ # # Checks for name style for argument of FactoryBot::Syntax::Methods.
9
+ # #
10
+ # # @example EnforcedStyle: symbol (default)
11
+ # # # bad
12
+ # # create('user')
13
+ # # build "user", username: "NAME"
14
+ # #
15
+ # # # good
16
+ # # create(:user)
17
+ # # build :user, username: "NAME"
18
+ # #
19
+ # # @example EnforcedStyle: string
20
+ # # # bad
21
+ # # create(:user)
22
+ # # build :user, username: "NAME"
23
+ # #
24
+ # # # good
25
+ # # create('user')
26
+ # # build "user", username: "NAME"
27
+ # #
28
+ # class FactoryNameStyle < ::RuboCop::Cop::Base; end
29
+ FactoryNameStyle = ::RuboCop::Cop::FactoryBot::FactoryNameStyle
71
30
  end
72
31
  end
73
32
  end
@@ -4,85 +4,51 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Use shorthands from `FactoryBot::Syntax::Methods` in your specs.
8
- #
9
- # @safety
10
- # The autocorrection is marked as unsafe because the cop
11
- # cannot verify whether you already include
12
- # `FactoryBot::Syntax::Methods` in your test suite.
13
- #
14
- # If you're using Rails, add the following configuration to
15
- # `spec/support/factory_bot.rb` and be sure to require that file in
16
- # `rails_helper.rb`:
17
- #
18
- # [source,ruby]
19
- # ----
20
- # RSpec.configure do |config|
21
- # config.include FactoryBot::Syntax::Methods
22
- # end
23
- # ----
24
- #
25
- # If you're not using Rails:
26
- #
27
- # [source,ruby]
28
- # ----
29
- # RSpec.configure do |config|
30
- # config.include FactoryBot::Syntax::Methods
31
- #
32
- # config.before(:suite) do
33
- # FactoryBot.find_definitions
34
- # end
35
- # end
36
- # ----
37
- #
38
- # @example
39
- # # bad
40
- # FactoryBot.create(:bar)
41
- # FactoryBot.build(:bar)
42
- # FactoryBot.attributes_for(:bar)
43
- #
44
- # # good
45
- # create(:bar)
46
- # build(:bar)
47
- # attributes_for(:bar)
48
- #
49
- class SyntaxMethods < Base
50
- extend AutoCorrector
51
- include InsideExampleGroup
52
- include RangeHelp
53
- include RuboCop::RSpec::FactoryBot::Language
54
-
55
- MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
56
-
57
- RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS
58
-
59
- def on_send(node)
60
- return unless factory_bot?(node.receiver)
61
- return unless inside_example_group?(node)
62
-
63
- message = format(MSG, method: node.method_name)
64
-
65
- add_offense(crime_scene(node), message: message) do |corrector|
66
- corrector.remove(offense(node))
67
- end
68
- end
69
-
70
- private
71
-
72
- def crime_scene(node)
73
- range_between(
74
- node.loc.expression.begin_pos,
75
- node.loc.selector.end_pos
76
- )
77
- end
78
-
79
- def offense(node)
80
- range_between(
81
- node.loc.expression.begin_pos,
82
- node.loc.selector.begin_pos
83
- )
84
- end
85
- end
7
+ # @!parse
8
+ # # Use shorthands from `FactoryBot::Syntax::Methods` in your specs.
9
+ # #
10
+ # # @safety
11
+ # # The autocorrection is marked as unsafe because the cop
12
+ # # cannot verify whether you already include
13
+ # # `FactoryBot::Syntax::Methods` in your test suite.
14
+ # #
15
+ # # If you're using Rails, add the following configuration to
16
+ # # `spec/support/factory_bot.rb` and be sure to require that file
17
+ # # in `rails_helper.rb`:
18
+ # #
19
+ # # [source,ruby]
20
+ # # ----
21
+ # # RSpec.configure do |config|
22
+ # # config.include FactoryBot::Syntax::Methods
23
+ # # end
24
+ # # ----
25
+ # #
26
+ # # If you're not using Rails:
27
+ # #
28
+ # # [source,ruby]
29
+ # # ----
30
+ # # RSpec.configure do |config|
31
+ # # config.include FactoryBot::Syntax::Methods
32
+ # #
33
+ # # config.before(:suite) do
34
+ # # FactoryBot.find_definitions
35
+ # # end
36
+ # # end
37
+ # # ----
38
+ # #
39
+ # # @example
40
+ # # # bad
41
+ # # FactoryBot.create(:bar)
42
+ # # FactoryBot.build(:bar)
43
+ # # FactoryBot.attributes_for(:bar)
44
+ # #
45
+ # # # good
46
+ # # create(:bar)
47
+ # # build(:bar)
48
+ # # attributes_for(:bar)
49
+ # #
50
+ # class SyntaxMethods < ::RuboCop::Cop::Base; end
51
+ SyntaxMethods = ::RuboCop::Cop::FactoryBot::SyntaxMethods
86
52
  end
87
53
  end
88
54
  end
@@ -5,8 +5,14 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks that spec file paths are consistent and well-formed.
7
7
  #
8
+ # This cop is deprecated.
9
+ # We plan to remove it in the next major version update to 3.0.
10
+ # The migration targets are `RSpec/SpecFilePathSuffix`
11
+ # and `RSpec/SpecFilePathFormat`.
12
+ # If you are using this cop, please plan for migration.
13
+ #
8
14
  # By default, this checks that spec file paths are consistent with the
9
- # test subject and and enforces that it reflects the described
15
+ # test subject and enforces that it reflects the described
10
16
  # class/module and its optionally called out method.
11
17
  #
12
18
  # With the configuration option `IgnoreMethods` the called out method will
@@ -165,7 +171,7 @@ module RuboCop
165
171
  end
166
172
 
167
173
  def expanded_file_path
168
- File.expand_path(processed_source.buffer.name)
174
+ File.expand_path(processed_source.file_path)
169
175
  end
170
176
  end
171
177
  end
@@ -34,6 +34,18 @@ module RuboCop
34
34
  # # good
35
35
  # describe 'test' do; end
36
36
  #
37
+ # # bad
38
+ # shared_examples 'test', focus: true do; end
39
+ #
40
+ # # good
41
+ # shared_examples 'test' do; end
42
+ #
43
+ # # bad
44
+ # shared_context 'test', focus: true do; end
45
+ #
46
+ # # good
47
+ # shared_context 'test' do; end
48
+ #
37
49
  # # bad (does not support autocorrection)
38
50
  # focus 'test' do; end
39
51
  #
@@ -51,6 +63,7 @@ module RuboCop
51
63
  #Examples.regular
52
64
  #Examples.skipped
53
65
  #Examples.pending
66
+ #SharedGroups.all
54
67
  }
55
68
  PATTERN
56
69
 
@@ -61,12 +74,13 @@ module RuboCop
61
74
  PATTERN
62
75
 
63
76
  # @!method focused_block?(node)
64
- def_node_matcher :focused_block?,
65
- send_pattern(<<~PATTERN)
66
- {#ExampleGroups.focused #Examples.focused}
67
- PATTERN
77
+ def_node_matcher :focused_block?, <<~PATTERN
78
+ (send #rspec? {#ExampleGroups.focused #Examples.focused} ...)
79
+ PATTERN
68
80
 
69
81
  def on_send(node)
82
+ return if node.chained? || node.each_ancestor(:def, :defs).any?
83
+
70
84
  focus_metadata(node) do |focus|
71
85
  add_offense(focus) do |corrector|
72
86
  if focus.pair_type? || focus.str_type? || focus.sym_type?
@@ -88,7 +102,7 @@ module RuboCop
88
102
 
89
103
  def with_surrounding(focus)
90
104
  range_with_space =
91
- range_with_surrounding_space(focus.loc.expression, side: :left)
105
+ range_with_surrounding_space(focus.source_range, side: :left)
92
106
 
93
107
  range_with_surrounding_comma(range_with_space, :left)
94
108
  end
@@ -83,8 +83,7 @@ module RuboCop
83
83
  style_detected(scope_name)
84
84
  msg = explicit_message(scope_name)
85
85
  add_offense(method_send, message: msg) do |corrector|
86
- scope = implicit_style? ? '' : "(#{style.inspect})"
87
- corrector.replace(argument_range(method_send), scope)
86
+ autocorrect(corrector, node, method_send)
88
87
  end
89
88
  end
90
89
  end
@@ -93,6 +92,13 @@ module RuboCop
93
92
 
94
93
  private
95
94
 
95
+ def autocorrect(corrector, _node, method_send)
96
+ scope = implicit_style? ? '' : "(#{style.inspect})"
97
+ corrector.replace(
98
+ LocationHelp.arguments_with_whitespace(method_send), scope
99
+ )
100
+ end
101
+
96
102
  def check_implicit(method_send)
97
103
  style_detected(:implicit)
98
104
  return if implicit_style?
@@ -100,7 +106,10 @@ module RuboCop
100
106
  msg = explicit_message(nil)
101
107
  add_offense(method_send.loc.selector, message: msg) do |corrector|
102
108
  scope = "(#{style.inspect})"
103
- corrector.replace(argument_range(method_send), scope)
109
+ corrector.replace(
110
+ LocationHelp.arguments_with_whitespace(method_send),
111
+ scope
112
+ )
104
113
  end
105
114
  end
106
115
 
@@ -119,12 +128,6 @@ module RuboCop
119
128
  def hook(node, &block)
120
129
  scoped_hook(node, &block) || unscoped_hook(node, &block)
121
130
  end
122
-
123
- def argument_range(send_node)
124
- send_node.loc.selector.end.with(
125
- end_pos: send_node.loc.expression.end_pos
126
- )
127
- end
128
131
  end
129
132
  end
130
133
  end
@@ -30,9 +30,11 @@ module RuboCop
30
30
  # @!method example_or_group?(node)
31
31
  def_node_matcher :example_or_group?, <<-PATTERN
32
32
  {
33
- #{block_pattern('{#ExampleGroups.all #Examples.all}')}
34
- #{numblock_pattern('{#ExampleGroups.all #Examples.all}')}
35
- #{send_pattern('#Includes.examples')}
33
+ ({block numblock} {
34
+ (send #rspec? #ExampleGroups.all ...)
35
+ (send nil? #Examples.all ...)
36
+ } ...)
37
+ (send nil? #Includes.examples ...)
36
38
  }
37
39
  PATTERN
38
40
 
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Do not set up test data using indexes (e.g., `item_1`, `item_2`).
7
+ #
8
+ # It makes reading the test harder because it's not clear what exactly
9
+ # is tested by this particular example.
10
+ #
11
+ # The configurable options `AllowedIdentifiers` and `AllowedPatterns`
12
+ # will also read those set in `Naming/VariableNumber`.
13
+ #
14
+ # @example `Max: 1 (default)`
15
+ # # bad
16
+ # let(:item_1) { create(:item) }
17
+ # let(:item_2) { create(:item) }
18
+ #
19
+ # let(:item1) { create(:item) }
20
+ # let(:item2) { create(:item) }
21
+ #
22
+ # # good
23
+ #
24
+ # let(:visible_item) { create(:item, visible: true) }
25
+ # let(:invisible_item) { create(:item, visible: false) }
26
+ #
27
+ # @example `Max: 2`
28
+ # # bad
29
+ # let(:item_1) { create(:item) }
30
+ # let(:item_2) { create(:item) }
31
+ # let(:item_3) { create(:item) }
32
+ #
33
+ # # good
34
+ # let(:item_1) { create(:item) }
35
+ # let(:item_2) { create(:item) }
36
+ #
37
+ # @example `AllowedIdentifiers: ['item_1', 'item_2']`
38
+ # # good
39
+ # let(:item_1) { create(:item) }
40
+ # let(:item_2) { create(:item) }
41
+ #
42
+ # @example `AllowedPatterns: ['item']`
43
+ # # good
44
+ # let(:item_1) { create(:item) }
45
+ # let(:item_2) { create(:item) }
46
+ #
47
+ class IndexedLet < Base
48
+ include AllowedIdentifiers
49
+ include AllowedPattern
50
+
51
+ MSG = 'This `let` statement uses index in its name. Please give it ' \
52
+ 'a meaningful name.'
53
+
54
+ # @!method let_name(node)
55
+ def_node_matcher :let_name, <<~PATTERN
56
+ {
57
+ (block (send nil? #Helpers.all ({str sym} $_) ...) ...)
58
+ (send nil? #Helpers.all ({str sym} $_) block_pass)
59
+ }
60
+ PATTERN
61
+
62
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
63
+ return unless spec_group?(node)
64
+
65
+ children = node.body&.child_nodes
66
+ return unless children
67
+
68
+ filter_indexed_lets(children).each do |let_node|
69
+ add_offense(let_node)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ SUFFIX_INDEX_REGEX = /_?\d+$/.freeze
76
+ INDEX_REGEX = /\d+/.freeze
77
+
78
+ def filter_indexed_lets(candidates)
79
+ candidates
80
+ .filter { |node| indexed_let?(node) }
81
+ .group_by { |node| let_name_stripped_index(node) }
82
+ .values
83
+ .filter { |lets| lets.length > cop_config['Max'] }
84
+ .flatten
85
+ end
86
+
87
+ def indexed_let?(node)
88
+ let?(node) &&
89
+ SUFFIX_INDEX_REGEX.match?(let_name(node)) &&
90
+ !allowed_identifier?(let_name(node).to_s) &&
91
+ !matches_allowed_pattern?(let_name(node).to_s)
92
+ end
93
+
94
+ def let_name_stripped_index(node)
95
+ let_name(node).to_s.gsub(INDEX_REGEX, '')
96
+ end
97
+
98
+ def cop_config_patterns_values
99
+ Array(config.for_cop('Naming/VariableNumber')
100
+ .fetch('AllowedPatterns', [])) +
101
+ Array(cop_config.fetch('AllowedPatterns', []))
102
+ end
103
+
104
+ def allowed_identifiers
105
+ Array(config.for_cop('Naming/VariableNumber')
106
+ .fetch('AllowedIdentifiers', [])) +
107
+ Array(cop_config.fetch('AllowedIdentifiers', []))
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  class InstanceVariable < Base
49
49
  include TopLevelGroup
50
50
 
51
- MSG = 'Avoid instance variables use let, ' \
51
+ MSG = 'Avoid instance variables - use let, ' \
52
52
  'a method call, or a local variable (if possible).'
53
53
 
54
54
  # @!method dynamic_class?(node)
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # Anonymous classes are fine, since they don't result in global
18
18
  # namespace name clashes.
19
19
  #
20
- # @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants
20
+ # @see https://rspec.info/features/3-12/rspec-mocks/mutating-constants
21
21
  #
22
22
  # @example Constants leak between examples
23
23
  # # bad
@@ -38,19 +38,23 @@ module RuboCop
38
38
  # @!method example_or_group?(node)
39
39
  def_node_matcher :example_or_group?, <<-PATTERN
40
40
  {
41
- #{block_pattern('{#ExampleGroups.all #Examples.all}')}
42
- #{send_pattern('#Includes.examples')}
41
+ (block (send nil? {#ExampleGroups.all #Examples.all} ...) ...)
42
+ (send nil? #Includes.examples ...)
43
43
  }
44
44
  PATTERN
45
45
 
46
46
  # @!method include_examples?(node)
47
47
  def_node_matcher :include_examples?, <<~PATTERN
48
48
  {
49
- #{block_pattern(':include_examples')}
50
- #{send_pattern(':include_examples')}
49
+ (block (send nil? :include_examples ...) ...)
50
+ (send nil? :include_examples ...)
51
51
  }
52
52
  PATTERN
53
53
 
54
+ def self.autocorrect_incompatible_with
55
+ [RSpec::ScatteredLet]
56
+ end
57
+
54
58
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
55
59
  return unless example_group_with_body?(node)
56
60
 
@@ -29,14 +29,12 @@ module RuboCop
29
29
  MSG = 'Do not use `let!` to setup objects not referenced in tests.'
30
30
 
31
31
  # @!method example_or_shared_group_or_including?(node)
32
- def_node_matcher :example_or_shared_group_or_including?,
33
- block_pattern(<<~PATTERN)
34
- {
35
- #SharedGroups.all
36
- #ExampleGroups.all
37
- #Includes.all
38
- }
39
- PATTERN
32
+ def_node_matcher :example_or_shared_group_or_including?, <<~PATTERN
33
+ (block {
34
+ (send #rspec? {#SharedGroups.all #ExampleGroups.all} ...)
35
+ (send nil? #Includes.all ...)
36
+ } ...)
37
+ PATTERN
40
38
 
41
39
  # @!method let_bang(node)
42
40
  def_node_matcher :let_bang, <<-PATTERN