rubocop-rspec 2.18.1 → 2.19.0

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