rubocop-rspec 2.18.1 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +1 -1
  4. data/config/default.yml +31 -0
  5. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  6. data/lib/rubocop/cop/rspec/change_by_zero.rb +3 -3
  7. data/lib/rubocop/cop/rspec/contain_exactly.rb +45 -0
  8. data/lib/rubocop/cop/rspec/context_wording.rb +13 -5
  9. data/lib/rubocop/cop/rspec/describe_method.rb +16 -8
  10. data/lib/rubocop/cop/rspec/described_class.rb +2 -1
  11. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +3 -1
  12. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  13. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +1 -1
  14. data/lib/rubocop/cop/rspec/empty_example_group.rb +7 -7
  15. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -2
  16. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  17. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -1
  18. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  19. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +1 -1
  20. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +2 -2
  21. data/lib/rubocop/cop/rspec/file_path.rb +1 -1
  22. data/lib/rubocop/cop/rspec/focus.rb +4 -5
  23. data/lib/rubocop/cop/rspec/hook_argument.rb +12 -9
  24. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +5 -3
  25. data/lib/rubocop/cop/rspec/let_before_examples.rb +4 -4
  26. data/lib/rubocop/cop/rspec/let_setup.rb +6 -8
  27. data/lib/rubocop/cop/rspec/match_array.rb +41 -0
  28. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +1 -2
  29. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  30. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +20 -4
  31. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -1
  32. data/lib/rubocop/cop/rspec/named_subject.rb +6 -4
  33. data/lib/rubocop/cop/rspec/no_expectation_example.rb +2 -5
  34. data/lib/rubocop/cop/rspec/overwriting_setup.rb +3 -1
  35. data/lib/rubocop/cop/rspec/pending.rb +12 -12
  36. data/lib/rubocop/cop/rspec/pending_without_reason.rb +65 -36
  37. data/lib/rubocop/cop/rspec/predicate_matcher.rb +9 -34
  38. data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +4 -4
  39. data/lib/rubocop/cop/rspec/rails/travel_around.rb +92 -0
  40. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  41. data/lib/rubocop/cop/rspec/redundant_around.rb +69 -0
  42. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +3 -6
  43. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +3 -6
  44. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -4
  45. data/lib/rubocop/cop/rspec/shared_context.rb +12 -13
  46. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -4
  47. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  48. data/lib/rubocop/cop/rspec/sort_metadata.rb +2 -2
  49. data/lib/rubocop/cop/rspec/variable_definition.rb +3 -0
  50. data/lib/rubocop/cop/rspec/variable_name.rb +4 -1
  51. data/lib/rubocop/cop/rspec/verified_double_reference.rb +3 -3
  52. data/lib/rubocop/cop/rspec_cops.rb +5 -0
  53. data/lib/rubocop/rspec/example_group.rb +6 -8
  54. data/lib/rubocop/rspec/language/node_pattern.rb +26 -0
  55. data/lib/rubocop/rspec/language.rb +25 -16
  56. data/lib/rubocop/rspec/version.rb +1 -1
  57. data/lib/rubocop-rspec.rb +1 -0
  58. metadata +9 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b2ef643f2d262910bba4db8e0e6a722b96d0cb6424d04d4261326155176f321
4
- data.tar.gz: 29ae9b634075bd3dc11d44969fb00a7542aad3302f3a1aa360a2637e3875a3bc
3
+ metadata.gz: 44e3a236fcd4869222a8e50780c5ef50671968fe82661b22f34d93315116c36b
4
+ data.tar.gz: 22c11a55d5d29ae10f2ec8fc4a3c95a3bd7f2b1bb5a0b049a9e47d3328178e7d
5
5
  SHA512:
6
- metadata.gz: b14f91843dcbc6cd8abfca82a8f21244991aacb79d09a92e975c1eb6d36e554adc44bd1d8b14d2d014fed60b6383f1e102f5142c4a771532be1b60db47c01df1
7
- data.tar.gz: 0626b43df05da5b5f386d18be195d2aaa14347a7f70cd99d2d1731e9f9107536c8a0076897b78d9f2f4e69cdeb2a79bc0caaab488a6bee822ea08add9b083fe9
6
+ metadata.gz: bbfab487662a025737c213ab0f9c0a4c54d1d93fe3a3c05a344b20dcd2f83900aef723f07d0ecdade3c616afa15bee4a6cb975345ea9d8a80e3ba0a64c715a05
7
+ data.tar.gz: a1f112ce509e7a938a7d0377ac2fcf0a5c723fbac2299f5246ab84b4d989d8223b7a608c5952368094515085d4a106695d3baf71b5f8b02769cdbbf4965d9cd4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 2.19.0 (2023-03-06)
6
+
7
+ - Fix a false positive for `RSpec/ContextWording` when context is interpolated string literal or execute string. ([@ydah])
8
+ - Fix a false positive for `RSpec/DescribeMethod` when multi-line describe without `#` and `.` at the beginning. ([@ydah], [@pirj])
9
+ - Fix a false positive for `RSpec/VariableName` when inside non-spec code. ([@ydah])
10
+ - Fix a false positive for `RSpec/VariableDefinition` when inside non-spec code. ([@ydah])
11
+ - Add new `RSpec/PendingBlockInsideExample` cop. ([@ydah])
12
+ - Add `RSpec/RedundantAround` cop. ([@r7kamura])
13
+ - Add `RSpec/Rails/TravelAround` cop. ([@r7kamura])
14
+ - Add `RSpec/ContainExactly` and `RSpec/MatchArray` cops. ([@faucct])
15
+ - Fix a false positive for `RSpec/PendingWithoutReason` when not inside example and pending/skip with block. ([@ydah], [@pirj])
16
+ - Fix a false positive for `RSpec/PendingWithoutReason` when `skip` is passed a block inside example. ([@ydah], [@pirj])
17
+ - Rename `RSpec/PendingBlockInsideExample` cop to `RSpec/SkipBlockInsideExample`. ([@pirj])
18
+ - Deprecate `send_pattern`/`block_pattern`/`numblock_pattern` helpers in favour of using node pattern explicitly. ([@pirj], [@ydah])
19
+ - Fix an incorrect autocorrect for `RSpec/VerifiedDoubleReference` when namespaced class. ([@ydah])
20
+
5
21
  ## 2.18.1 (2023-01-19)
6
22
 
7
23
  - Add `rubocop-capybara` version constraint to prevent sudden cop enabling when it hits 3.0. ([@pirj])
@@ -751,6 +767,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
751
767
  [@elebow]: https://github.com/elebow
752
768
  [@elisefitz15]: https://github.com/EliseFitz15
753
769
  [@elliterate]: https://github.com/elliterate
770
+ [@faucct]: https://github.com/faucct
754
771
  [@foton]: https://github.com/foton
755
772
  [@francois-ferrandis]: https://github.com/francois-ferrandis
756
773
  [@g-rath]: https://github.com/G-Rath
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Gem Version](https://badge.fury.io/rb/rubocop-rspec.svg)](https://rubygems.org/gems/rubocop-rspec)
5
5
  ![CI](https://github.com/rubocop/rubocop-rspec/workflows/CI/badge.svg)
6
6
 
7
- RSpec-specific analysis for your projects, as an extension to
7
+ [RSpec](https://rspec.info/)-specific analysis for your projects, as an extension to
8
8
  [RuboCop](https://github.com/rubocop/rubocop).
9
9
 
10
10
  ## Installation
data/config/default.yml CHANGED
@@ -201,6 +201,12 @@ RSpec/ClassCheck:
201
201
  - be_kind_of
202
202
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ClassCheck
203
203
 
204
+ RSpec/ContainExactly:
205
+ Description: Prefer `match_array` when matching array values.
206
+ Enabled: true
207
+ VersionAdded: '2.19'
208
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContainExactly
209
+
204
210
  RSpec/ContextMethod:
205
211
  Description: "`context` should not be used for specifying methods."
206
212
  Enabled: true
@@ -556,6 +562,12 @@ RSpec/LetSetup:
556
562
  VersionAdded: '1.7'
557
563
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetSetup
558
564
 
565
+ RSpec/MatchArray:
566
+ Description: Prefer `contain_exactly` when matching an array literal.
567
+ Enabled: true
568
+ VersionAdded: '2.19'
569
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchArray
570
+
559
571
  RSpec/MessageChain:
560
572
  Description: Check that chains of messages are not being stubbed.
561
573
  Enabled: true
@@ -706,6 +718,12 @@ RSpec/ReceiveNever:
706
718
  VersionAdded: '1.28'
707
719
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveNever
708
720
 
721
+ RSpec/RedundantAround:
722
+ Description: Remove redundant `around` hook.
723
+ Enabled: pending
724
+ VersionAdded: '2.19'
725
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantAround
726
+
709
727
  RSpec/RepeatedDescription:
710
728
  Description: Check for repeated description strings in example groups.
711
729
  Enabled: true
@@ -779,6 +797,12 @@ RSpec/SingleArgumentMessageChain:
779
797
  VersionChanged: '1.10'
780
798
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain
781
799
 
800
+ RSpec/SkipBlockInsideExample:
801
+ Description: Checks for passing a block to `skip` within examples.
802
+ Enabled: pending
803
+ VersionAdded: '2.19'
804
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SkipBlockInsideExample
805
+
782
806
  RSpec/SortMetadata:
783
807
  Description: Sort RSpec metadata alphabetically.
784
808
  Enabled: pending
@@ -1056,3 +1080,10 @@ RSpec/Rails/MinitestAssertions:
1056
1080
  Enabled: pending
1057
1081
  VersionAdded: '2.17'
1058
1082
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions
1083
+
1084
+ RSpec/Rails/TravelAround:
1085
+ Description: Prefer to travel in `before` rather than `around`.
1086
+ Enabled: pending
1087
+ Safe: false
1088
+ VersionAdded: '2.19'
1089
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/TravelAround
@@ -57,7 +57,7 @@ module RuboCop
57
57
  return unless be_nil_matcher?(node)
58
58
 
59
59
  add_offense(node, message: BE_MSG) do |corrector|
60
- corrector.replace(node.loc.expression, 'be(nil)')
60
+ corrector.replace(node.source_range, 'be(nil)')
61
61
  end
62
62
  end
63
63
 
@@ -65,7 +65,7 @@ module RuboCop
65
65
  return unless nil_value_expectation?(node)
66
66
 
67
67
  add_offense(node, message: BE_NIL_MSG) do |corrector|
68
- corrector.replace(node.loc.expression, 'be_nil')
68
+ corrector.replace(node.source_range, 'be_nil')
69
69
  end
70
70
  end
71
71
  end
@@ -99,7 +99,7 @@ module RuboCop
99
99
  private
100
100
 
101
101
  def check_offense(node)
102
- expression = node.loc.expression
102
+ expression = node.source_range
103
103
  if compound_expectations?(node)
104
104
  add_offense(expression, message: message_compound) do |corrector|
105
105
  autocorrect_compound(corrector, node)
@@ -117,7 +117,7 @@ module RuboCop
117
117
 
118
118
  def autocorrect(corrector, node)
119
119
  corrector.replace(node.parent.loc.selector, 'not_to')
120
- range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
120
+ range = node.loc.dot.with(end_pos: node.source_range.end_pos)
121
121
  corrector.remove(range)
122
122
  end
123
123
 
@@ -126,7 +126,7 @@ module RuboCop
126
126
 
127
127
  change_nodes(node) do |change_node|
128
128
  corrector.replace(change_node.loc.selector, negated_matcher)
129
- range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
129
+ range = node.loc.dot.with(end_pos: node.source_range.end_pos)
130
130
  corrector.remove(range)
131
131
  end
132
132
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Prefer `match_array` when matching array values.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it { is_expected.to contain_exactly(*array1, *array2) }
11
+ #
12
+ # # good
13
+ # it { is_expected.to match_array(array1 + array2) }
14
+ #
15
+ # # good
16
+ # it { is_expected.to contain_exactly(content, *array) }
17
+ class ContainExactly < Base
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Prefer `match_array` when matching array values.'
21
+ RESTRICT_ON_SEND = %i[contain_exactly].freeze
22
+
23
+ def on_send(node)
24
+ return unless node.each_child_node.all?(&:splat_type?)
25
+
26
+ add_offense(node) do |corrector|
27
+ autocorrect(node, corrector)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def autocorrect(node, corrector)
34
+ arrays = node.arguments.map do |splat_node|
35
+ splat_node.children.first
36
+ end
37
+ corrector.replace(
38
+ node.source_range,
39
+ "match_array(#{arrays.map(&:source).join(' + ')})"
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -62,12 +62,12 @@ module RuboCop
62
62
 
63
63
  # @!method context_wording(node)
64
64
  def_node_matcher :context_wording, <<-PATTERN
65
- (block (send #rspec? { :context :shared_context } $(str $_) ...) ...)
65
+ (block (send #rspec? { :context :shared_context } $({str dstr xstr} ...) ...) ...)
66
66
  PATTERN
67
67
 
68
68
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
69
- context_wording(node) do |context, description|
70
- if bad_pattern?(description)
69
+ context_wording(node) do |context|
70
+ if bad_pattern?(context)
71
71
  message = format(MSG, patterns: expect_patterns)
72
72
  add_offense(context, message: message)
73
73
  end
@@ -84,10 +84,18 @@ module RuboCop
84
84
  @prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ }
85
85
  end
86
86
 
87
- def bad_pattern?(description)
87
+ def bad_pattern?(node)
88
88
  return false if allowed_patterns.empty?
89
89
 
90
- !matches_allowed_pattern?(description)
90
+ !matches_allowed_pattern?(description(node))
91
+ end
92
+
93
+ def description(context)
94
+ if context.xstr_type?
95
+ context.value.value
96
+ else
97
+ context.value
98
+ end
91
99
  end
92
100
 
93
101
  def expect_patterns
@@ -23,20 +23,28 @@ module RuboCop
23
23
  MSG = 'The second argument to describe should be the method ' \
24
24
  "being tested. '#instance' or '.class'."
25
25
 
26
- # @!method second_argument(node)
27
- def_node_matcher :second_argument, <<~PATTERN
26
+ # @!method second_string_literal_argument(node)
27
+ def_node_matcher :second_string_literal_argument, <<~PATTERN
28
28
  (block
29
- (send #rspec? :describe _first_argument $(str _) ...) ...
30
- )
29
+ (send #rspec? :describe _first_argument ${str dstr} ...)
30
+ ...)
31
+ PATTERN
32
+
33
+ # @!method method_name?(node)
34
+ def_node_matcher :method_name?, <<~PATTERN
35
+ {(str #method_name_prefix?) (dstr (str #method_name_prefix?) ...)}
31
36
  PATTERN
32
37
 
33
38
  def on_top_level_group(node)
34
- second_argument = second_argument(node)
39
+ second_string_literal_argument(node) do |argument|
40
+ add_offense(argument) unless method_name?(argument)
41
+ end
42
+ end
35
43
 
36
- return unless second_argument
37
- return if second_argument.str_content.start_with?('#', '.')
44
+ private
38
45
 
39
- add_offense(second_argument)
46
+ def method_name_prefix?(description)
47
+ description.start_with?('.', '#')
40
48
  end
41
49
  end
42
50
  end
@@ -68,7 +68,8 @@ module RuboCop
68
68
  PATTERN
69
69
 
70
70
  # @!method rspec_block?(node)
71
- def_node_matcher :rspec_block?, block_pattern('#ALL.all')
71
+ def_node_matcher :rspec_block?,
72
+ '({block numblock} (send #rspec? #ALL.all ...) ...)'
72
73
 
73
74
  # @!method scope_changing_syntax?(node)
74
75
  def_node_matcher :scope_changing_syntax?, '{def class module}'
@@ -23,7 +23,9 @@ module RuboCop
23
23
  MSG = 'Avoid opening modules and defining specs within them.'
24
24
 
25
25
  # @!method find_rspec_blocks(node)
26
- def_node_search :find_rspec_blocks, block_pattern('#ExampleGroups.all')
26
+ def_node_search :find_rspec_blocks, <<~PATTERN
27
+ (block (send #explicit_rspec? #ExampleGroups.all ...) ...)
28
+ PATTERN
27
29
 
28
30
  def on_module(node)
29
31
  find_rspec_blocks(node) do
@@ -49,7 +49,7 @@ module RuboCop
49
49
  MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
50
50
 
51
51
  # @!method rspec_method?(node)
52
- def_node_matcher :rspec_method?, send_pattern('#ALL.all')
52
+ def_node_matcher :rspec_method?, '(send #rspec? #ALL.all ...)'
53
53
 
54
54
  def on_send(node)
55
55
  return unless rspec_method?(node)
@@ -39,7 +39,7 @@ module RuboCop
39
39
  corrector.remove(
40
40
  range_with_surrounding_comma(
41
41
  range_with_surrounding_space(
42
- node.location.expression,
42
+ node.source_range,
43
43
  side: :left
44
44
  ),
45
45
  :left
@@ -53,7 +53,7 @@ module RuboCop
53
53
  # @param node [RuboCop::AST::Node]
54
54
  # @yield [RuboCop::AST::Node] example group body
55
55
  def_node_matcher :example_group_body, <<~PATTERN
56
- (block #{send_pattern('#ExampleGroups.all')} args $_)
56
+ (block (send #rspec? #ExampleGroups.all ...) args $_)
57
57
  PATTERN
58
58
 
59
59
  # @!method example_or_group_or_include?(node)
@@ -72,10 +72,10 @@ module RuboCop
72
72
  # @return [Array<RuboCop::AST::Node>] matching nodes
73
73
  def_node_matcher :example_or_group_or_include?, <<~PATTERN
74
74
  {
75
- #{block_pattern(
76
- '{#Examples.all #ExampleGroups.all #Includes.all}'
77
- )}
78
- #{send_pattern('{#Examples.all #Includes.all}')}
75
+ (block
76
+ (send #rspec? {#Examples.all #ExampleGroups.all #Includes.all} ...)
77
+ ...)
78
+ (send nil? {#Examples.all #Includes.all} ...)
79
79
  }
80
80
  PATTERN
81
81
 
@@ -95,7 +95,7 @@ module RuboCop
95
95
  # @param node [RuboCop::AST::Node]
96
96
  # @return [Array<RuboCop::AST::Node>] matching nodes
97
97
  def_node_matcher :examples_inside_block?, <<~PATTERN
98
- (block !#{send_pattern('#Hooks.all')} _ #examples?)
98
+ (block !(send nil? #Hooks.all ...) _ #examples?)
99
99
  PATTERN
100
100
 
101
101
  # @!method examples_directly_or_in_block?(node)
@@ -174,7 +174,7 @@ module RuboCop
174
174
 
175
175
  def removed_range(node)
176
176
  range_by_whole_lines(
177
- node.location.expression,
177
+ node.source_range,
178
178
  include_final_newline: true
179
179
  )
180
180
  end
@@ -31,14 +31,14 @@ module RuboCop
31
31
 
32
32
  # @!method empty_hook?(node)
33
33
  def_node_matcher :empty_hook?, <<~PATTERN
34
- (block $#{send_pattern('#Hooks.all')} _ nil?)
34
+ (block $(send nil? #Hooks.all ...) _ nil?)
35
35
  PATTERN
36
36
 
37
37
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
38
38
  empty_hook?(node) do |hook|
39
39
  add_offense(hook) do |corrector|
40
40
  corrector.remove(
41
- range_with_surrounding_space(node.loc.expression, side: :left)
41
+ range_with_surrounding_space(node.source_range, side: :left)
42
42
  )
43
43
  end
44
44
  end
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
 
90
90
  def docstring(node)
91
- expr = node.loc.expression
91
+ expr = node.source_range
92
92
 
93
93
  Parser::Source::Range.new(
94
94
  expr.source_buffer,
@@ -74,7 +74,7 @@ module RuboCop
74
74
  end
75
75
 
76
76
  def docstring(node)
77
- expr = node.loc.expression
77
+ expr = node.source_range
78
78
 
79
79
  Parser::Source::Range.new(
80
80
  expr.source_buffer,
@@ -25,7 +25,7 @@ module RuboCop
25
25
  MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'
26
26
 
27
27
  # @!method expectation(node)
28
- def_node_search :expectation, send_pattern('#Expectations.all')
28
+ def_node_search :expectation, '(send nil? #Expectations.all ...)'
29
29
 
30
30
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
31
31
  return unless hook?(node)
@@ -96,7 +96,7 @@ module RuboCop
96
96
  left_braces, right_braces = braces(node)
97
97
 
98
98
  argument = node.first_argument
99
- expression = argument.location.expression
99
+ expression = argument.source_range
100
100
  corrector.insert_before(expression, left_braces)
101
101
  corrector.insert_after(expression, right_braces)
102
102
  end
@@ -71,14 +71,14 @@ module RuboCop
71
71
 
72
72
  def crime_scene(node)
73
73
  range_between(
74
- node.loc.expression.begin_pos,
74
+ node.source_range.begin_pos,
75
75
  node.loc.selector.end_pos
76
76
  )
77
77
  end
78
78
 
79
79
  def offense(node)
80
80
  range_between(
81
- node.loc.expression.begin_pos,
81
+ node.source_range.begin_pos,
82
82
  node.loc.selector.begin_pos
83
83
  )
84
84
  end
@@ -165,7 +165,7 @@ module RuboCop
165
165
  end
166
166
 
167
167
  def expanded_file_path
168
- File.expand_path(processed_source.buffer.name)
168
+ File.expand_path(processed_source.file_path)
169
169
  end
170
170
  end
171
171
  end
@@ -61,10 +61,9 @@ module RuboCop
61
61
  PATTERN
62
62
 
63
63
  # @!method focused_block?(node)
64
- def_node_matcher :focused_block?,
65
- send_pattern(<<~PATTERN)
66
- {#ExampleGroups.focused #Examples.focused}
67
- PATTERN
64
+ def_node_matcher :focused_block?, <<~PATTERN
65
+ (send #rspec? {#ExampleGroups.focused #Examples.focused} ...)
66
+ PATTERN
68
67
 
69
68
  def on_send(node)
70
69
  focus_metadata(node) do |focus|
@@ -88,7 +87,7 @@ module RuboCop
88
87
 
89
88
  def with_surrounding(focus)
90
89
  range_with_space =
91
- range_with_surrounding_space(focus.loc.expression, side: :left)
90
+ range_with_surrounding_space(focus.source_range, side: :left)
92
91
 
93
92
  range_with_surrounding_comma(range_with_space, :left)
94
93
  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
 
@@ -38,16 +38,16 @@ 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
 
@@ -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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Prefer `contain_exactly` when matching an array literal.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it { is_expected.to match_array([content1, content2]) }
11
+ #
12
+ # # good
13
+ # it { is_expected.to contain_exactly(content1, content2) }
14
+ #
15
+ # # good
16
+ # it { is_expected.to match_array([content] + array) }
17
+ #
18
+ # # good
19
+ # it { is_expected.to match_array(%w(tremble in fear foolish mortals)) }
20
+ class MatchArray < Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Prefer `contain_exactly` when matching an array literal.'
24
+ RESTRICT_ON_SEND = %i[match_array].freeze
25
+
26
+ def on_send(node)
27
+ return unless node.first_argument.array_type?
28
+ return if node.first_argument.percent_literal?
29
+
30
+ add_offense(node) do |corrector|
31
+ array_contents = node.arguments.flat_map(&:to_a)
32
+ corrector.replace(
33
+ node.source_range,
34
+ "contain_exactly(#{array_contents.map(&:source).join(', ')})"
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -25,8 +25,7 @@ module RuboCop
25
25
 
26
26
  def missing_separating_line(node)
27
27
  line = final_end_line = final_end_location(node).line
28
-
29
- while comment_line?(processed_source[line])
28
+ while processed_source.line_with_comment?(line + 1)
30
29
  line += 1
31
30
  comment = processed_source.comment_at_line(line)
32
31
  if DirectiveComment.new(comment).enabled?