rspec-expectations 3.5.0 → 3.9.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 (37) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +138 -2
  5. data/README.md +37 -20
  6. data/lib/rspec/expectations.rb +2 -1
  7. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  8. data/lib/rspec/expectations/configuration.rb +14 -0
  9. data/lib/rspec/expectations/expectation_target.rb +2 -2
  10. data/lib/rspec/expectations/fail_with.rb +9 -1
  11. data/lib/rspec/expectations/handler.rb +2 -2
  12. data/lib/rspec/expectations/minitest_integration.rb +1 -1
  13. data/lib/rspec/expectations/syntax.rb +2 -2
  14. data/lib/rspec/expectations/version.rb +1 -1
  15. data/lib/rspec/matchers.rb +97 -97
  16. data/lib/rspec/matchers/built_in/all.rb +1 -0
  17. data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
  18. data/lib/rspec/matchers/built_in/be.rb +2 -2
  19. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  20. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  21. data/lib/rspec/matchers/built_in/change.rb +127 -53
  22. data/lib/rspec/matchers/built_in/compound.rb +6 -2
  23. data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
  24. data/lib/rspec/matchers/built_in/exist.rb +5 -1
  25. data/lib/rspec/matchers/built_in/has.rb +1 -1
  26. data/lib/rspec/matchers/built_in/include.rb +6 -0
  27. data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
  28. data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
  29. data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
  30. data/lib/rspec/matchers/built_in/yield.rb +43 -30
  31. data/lib/rspec/matchers/composable.rb +6 -20
  32. data/lib/rspec/matchers/dsl.rb +72 -4
  33. data/lib/rspec/matchers/english_phrasing.rb +3 -3
  34. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
  35. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  36. metadata +22 -18
  37. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 564420270880a852a9f86af59b32445beff85990
4
- data.tar.gz: 68e50116cd97b1137f8ed688de17db436464b7b5
2
+ SHA256:
3
+ metadata.gz: d8cb915ab51f5b587fcbaa864e697cc291ff3c1f49afd8d829837c1a9c4c4f6b
4
+ data.tar.gz: 6aa770883cacbfcf2921074a98506a0a789b4557ee6a7ea79738f008107e582d
5
5
  SHA512:
6
- metadata.gz: 18a464c2ffba3b59d6c998419b36462177b5246fd9a66cb73c693358b9985bdffeb6fd4cafc97beff928ced458cb4e70fd78731b2e35a2e7f0aca89d4b1a0e0e
7
- data.tar.gz: b56c7c4cad9670fdf2925a700de34f1be6f14d2d197a12efba71f4f816e0a9a3f4f7c172c019b46ccab5344fe464362642ca53cbe522143fde63ad99d038978b
6
+ metadata.gz: fc2669ca822767874cc62c9e7cef32040f90da7cd0d4c13b1bd97fde68230f7f7019a8b717e2ebfcd223da5b9e25aaa60270c0797ba4d304def5e4ae3bb63f03
7
+ data.tar.gz: e9d7a9e1b186c1eee547c315f177181992f5be9e35c61a16a041d97901537d054943a779e0d6bd7f2502e51a1fcbf95f0f6a10eab46fcdd85bebd8be1170a369
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -1,7 +1,143 @@
1
+ ### 3.9.0 / 2019-10-02
2
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.5...v3.9.0)
3
+
4
+ Enhancements:
5
+
6
+ * The `respond_to` matcher now uses the signature from `initialize` to validate checks
7
+ for `new` (unless `new` is non standard). (Jon Rowe, #1072)
8
+ * Generated descriptions for matchers now use `is expected to` rather than `should` in
9
+ line with our preferred DSL. (Pete Johns, #1080, rspec/rspec-core#2572)
10
+ * Add the ability to re-raise expectation errors when matching
11
+ with `match_when_negated` blocks. (Jon Rowe, #1130)
12
+ * Add a warning when an empty diff is produce due to identical inspect output.
13
+ (Benoit Tigeot, #1126)
14
+
15
+ ### 3.8.6 / 2019-10-07
16
+
17
+ Bug Fixes:
18
+
19
+ * Revert #1125 due to the change being incompatible with our semantic versioning
20
+ policy.
21
+
22
+ ### 3.8.5 / 2019-10-02
23
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.4...v3.8.5)
24
+
25
+ Bug Fixes:
26
+
27
+ * Prevent unsupported implicit block expectation syntax from being used.
28
+ (Phil Pirozhkov, #1125)
29
+
30
+ ### 3.8.4 / 2019-06-10
31
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.3...v3.8.4)
32
+
33
+ Bug Fixes:
34
+
35
+ * Prevent false negatives when checking objects for the methods required to run the
36
+ the `be_an_instance_of` and `be_kind_of` matchers. (Nazar Matus, #1112)
37
+
38
+ ### 3.8.3 / 2019-04-20
39
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.2...v3.8.3)
40
+
41
+ Bug Fixes:
42
+
43
+ * Prevent composed `all` matchers from leaking into their siblings leading to duplicate
44
+ failures. (Jamie English, #1086)
45
+ * Prevent objects which change their hash on comparison from failing change checks.
46
+ (Phil Pirozhkov, #1110)
47
+ * Issue an `ArgumentError` rather than a `NoMethodError` when `be_an_instance_of` and
48
+ `be_kind_of` matchers encounter objects not supporting those methods.
49
+ (Taichi Ishitani, #1107)
50
+
51
+ ### 3.8.2 / 2018-10-09
52
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.1...v3.8.2)
53
+
54
+ Bug Fixes:
55
+
56
+ * Change `include` matcher to rely on a `respond_to?(:include?)` check rather than a direct
57
+ Hash comparison before calling `to_hash` to convert to a hash. (Jordan Owens, #1073)
58
+ * Prevent unexpected call stack jumps from causing an obscure error (`IndexError`), and
59
+ replace that error with a proper informative message. (Jon Rowe, #1076)
60
+
61
+ ### 3.8.1 / 2018-08-06
62
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.0...v3.8.1)
63
+
64
+ Bug Fixes:
65
+
66
+ * Fix regression in `include` matcher so stopped
67
+ `expect(hash.with_indifferent_access).to include(:symbol_key)`
68
+ from working. (Eito Katagiri, #1069)
69
+
70
+ ### 3.8.0 / 2018-08-04
71
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.7.0...v3.8.0)
72
+
73
+ Enhancements:
74
+
75
+ * Improve failure message of `change(receiver, :message)` by including the
76
+ receiver as `SomeClass#some_message`. (Tomohiro Hashidate, #1005)
77
+ * Improve `change` matcher so that it can correctly detect changes in
78
+ deeply nested mutable objects (such as arrays-of-hashes-of-arrays).
79
+ The improved logic uses the before/after `hash` value to see if the
80
+ object has been mutated, rather than shallow duping the object.
81
+ (Myron Marston, #1034)
82
+ * Improve `include` matcher so that pseudo-hash objects (e.g. objects
83
+ that decorate a hash using a `SimpleDelegator` or similar) are treated
84
+ as a hash, as long as they implement `to_hash`. (Pablo Brasero, #1012)
85
+ * Add `max_formatted_output_length=` to configuration, allowing changing
86
+ the length at which we truncate large output strings.
87
+ (Sam Phippen #951, Benoit Tigeot #1056)
88
+ * Improve error message when passing a matcher that doesn't support block
89
+ expectations to a block based `expect`. (@nicktime, #1066)
90
+
91
+ ### 3.7.0 / 2017-10-17
92
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0...v3.7.0)
93
+
94
+ Enhancements:
95
+
96
+ * Improve compatibility with `--enable-frozen-string-literal` option
97
+ on Ruby 2.3+. (Pat Allan, #997)
98
+
99
+ ### 3.6.0 / 2017-05-04
100
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta2...v3.6.0)
101
+
102
+ Enhancements:
103
+
104
+ * Treat NoMethodError as a failure for comparison matchers. (Jon Rowe, #972)
105
+ * Allow for scoped aliased and negated matchers--just call
106
+ `alias_matcher` or `define_negated_matcher` from within an example
107
+ group. (Markus Reiter, #974)
108
+ * Improve failure message of `change` matcher with block and `satisfy` matcher
109
+ by including the block snippet instead of just describing it as `result` or
110
+ `block` when Ripper is available. (Yuji Nakayama, #987)
111
+
112
+ Bug Fixes:
113
+
114
+ * Fix `yield_with_args` and `yield_successive_args` matchers so that
115
+ they compare expected to actual args at the time the args are yielded
116
+ instead of at the end, in case the method that is yielding mutates the
117
+ arguments after yielding. (Alyssa Ross, #965)
118
+
119
+ ### 3.6.0.beta2 / 2016-12-12
120
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta1...v3.6.0.beta2)
121
+
122
+ Bug Fixes:
123
+
124
+ * Using the exist matcher on `File` no longer produces a deprecation warning.
125
+ (Jon Rowe, #954)
126
+
127
+ ### 3.6.0.beta1 / 2016-10-09
128
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0...v3.6.0.beta1)
129
+
130
+ Bug Fixes:
131
+
132
+ * Fix `contain_exactly` to work correctly with ranges. (Myron Marston, #940)
133
+ * Fix `change` to work correctly with sets. (Marcin Gajewski, #939)
134
+
1
135
  ### 3.5.0 / 2016-07-01
2
136
  [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta4...v3.5.0)
3
137
 
4
- **No user facing changes since beta4**
138
+ Enhancements:
139
+
140
+ * Add support for keyword arguments to the `respond_to` matcher. (Rob Smith, #915).
5
141
 
6
142
  ### 3.5.0.beta4 / 2016-06-05
7
143
  [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta3...v3.5.0.beta4)
@@ -74,7 +210,7 @@ Bug Fixes:
74
210
 
75
211
  * Fix failure message from dynamic predicate matchers when the object
76
212
  does not respond to the predicate so that it is inspected rather
77
- than relying upon it's `to_s` -- that way for `nil`, `"nil"` is
213
+ than relying upon its `to_s` -- that way for `nil`, `"nil"` is
78
214
  printed rather than an empty string. (Myron Marston, #841)
79
215
  * Fix SystemStackError raised when diffing an Enumerable object
80
216
  whose `#each` includes the object itself. (Yuji Nakayama, #857)
data/README.md CHANGED
@@ -3,7 +3,9 @@
3
3
  RSpec::Expectations lets you express expected outcomes on an object in an
4
4
  example.
5
5
 
6
- expect(account.balance).to eq(Money.new(37.42, :USD))
6
+ ```ruby
7
+ expect(account.balance).to eq(Money.new(37.42, :USD))
8
+ ```
7
9
 
8
10
  ## Install
9
11
 
@@ -18,7 +20,7 @@ RSpec repos as well. Add the following to your `Gemfile`:
18
20
 
19
21
  ```ruby
20
22
  %w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
21
- gem lib, :git => "git://github.com/rspec/#{lib}.git", :branch => 'master'
23
+ gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'master'
22
24
  end
23
25
  ```
24
26
 
@@ -150,7 +152,7 @@ expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args
150
152
  expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded
151
153
 
152
154
  expect { |b| 5.tap(&b) }.to yield_with_args(5)
153
- expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
155
+ expect { |b| 5.tap(&b) }.to yield_with_args(Integer)
154
156
  expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
155
157
 
156
158
  expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
@@ -173,30 +175,45 @@ expect(1..10).to cover(3)
173
175
  ### Collection membership
174
176
 
175
177
  ```ruby
176
- expect(actual).to include(expected)
178
+ # exact order, entire collection
179
+ expect(actual).to eq(expected)
180
+
181
+ # exact order, partial collection (based on an exact position)
177
182
  expect(actual).to start_with(expected)
178
183
  expect(actual).to end_with(expected)
179
184
 
180
- expect(actual).to contain_exactly(individual, items)
181
- # ...which is the same as:
182
- expect(actual).to match_array(expected_array)
185
+ # any order, entire collection
186
+ expect(actual).to match_array(expected)
187
+
188
+ # You can also express this by passing the expected elements
189
+ # as individual arguments
190
+ expect(actual).to contain_exactly(expected_element1, expected_element2)
191
+
192
+ # any order, partial collection
193
+ expect(actual).to include(expected)
183
194
  ```
184
195
 
185
196
  #### Examples
186
197
 
187
198
  ```ruby
188
- expect([1, 2, 3]).to include(1)
189
- expect([1, 2, 3]).to include(1, 2)
190
- expect([1, 2, 3]).to start_with(1)
191
- expect([1, 2, 3]).to start_with(1, 2)
192
- expect([1, 2, 3]).to end_with(3)
193
- expect([1, 2, 3]).to end_with(2, 3)
194
- expect({:a => 'b'}).to include(:a => 'b')
195
- expect("this string").to include("is str")
196
- expect("this string").to start_with("this")
197
- expect("this string").to end_with("ring")
198
- expect([1, 2, 3]).to contain_exactly(2, 3, 1)
199
- expect([1, 2, 3]).to match_array([3, 2, 1])
199
+ expect([1, 2, 3]).to eq([1, 2, 3]) # Order dependent equality check
200
+ expect([1, 2, 3]).to include(1) # Exact ordering, partial collection matches
201
+ expect([1, 2, 3]).to include(2, 3) #
202
+ expect([1, 2, 3]).to start_with(1) # As above, but from the start of the collection
203
+ expect([1, 2, 3]).to start_with(1, 2) #
204
+ expect([1, 2, 3]).to end_with(3) # As above but from the end of the collection
205
+ expect([1, 2, 3]).to end_with(2, 3) #
206
+ expect({:a => 'b'}).to include(:a => 'b') # Matching within hashes
207
+ expect("this string").to include("is str") # Matching within strings
208
+ expect("this string").to start_with("this") #
209
+ expect("this string").to end_with("ring") #
210
+ expect([1, 2, 3]).to contain_exactly(2, 3, 1) # Order independent matches
211
+ expect([1, 2, 3]).to match_array([3, 2, 1]) #
212
+
213
+ # Order dependent compound matchers
214
+ expect(
215
+ [{:a => 'hash'},{:a => 'another'}]
216
+ ).to match([a_hash_including(:a => 'hash'), a_hash_including(:a => 'another')])
200
217
  ```
201
218
 
202
219
  ## `should` syntax
@@ -263,7 +280,7 @@ expect(hash).to match(
263
280
  :a => {
264
281
  :b => a_collection_containing_exactly(
265
282
  a_string_starting_with("f"),
266
- an_instance_of(Fixnum)
283
+ an_instance_of(Integer)
267
284
  ),
268
285
  :c => { :d => (a_value < 3) }
269
286
  }
@@ -76,6 +76,7 @@ module RSpec
76
76
  class MultipleExpectationsNotMetError < ExpectationNotMetError
77
77
  end
78
78
 
79
- autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
79
+ autoload :BlockSnippetExtractor, "rspec/expectations/block_snippet_extractor"
80
+ autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
80
81
  end
81
82
  end
@@ -0,0 +1,253 @@
1
+ module RSpec
2
+ module Expectations
3
+ # @private
4
+ class BlockSnippetExtractor # rubocop:disable Metrics/ClassLength
5
+ # rubocop should properly handle `Struct.new {}` as an inner class definition.
6
+
7
+ attr_reader :proc, :method_name
8
+
9
+ def self.try_extracting_single_line_body_of(proc, method_name)
10
+ lines = new(proc, method_name).body_content_lines
11
+ return nil unless lines.count == 1
12
+ lines.first
13
+ rescue Error
14
+ nil
15
+ end
16
+
17
+ def initialize(proc, method_name)
18
+ @proc = proc
19
+ @method_name = method_name.to_s.freeze
20
+ end
21
+
22
+ # Ideally we should properly handle indentations of multiline snippet,
23
+ # but it's not implemented yet since because we use result of this method only when it's a
24
+ # single line and implementing the logic introduces additional complexity.
25
+ def body_content_lines
26
+ raw_body_lines.map(&:strip).reject(&:empty?)
27
+ end
28
+
29
+ private
30
+
31
+ def raw_body_lines
32
+ raw_body_snippet.split("\n")
33
+ end
34
+
35
+ def raw_body_snippet
36
+ block_token_extractor.body_tokens.map(&:string).join
37
+ end
38
+
39
+ def block_token_extractor
40
+ @block_token_extractor ||= BlockTokenExtractor.new(method_name, source, beginning_line_number)
41
+ end
42
+
43
+ if RSpec.respond_to?(:world)
44
+ def source
45
+ raise TargetNotFoundError unless File.exist?(file_path)
46
+ RSpec.world.source_from_file(file_path)
47
+ end
48
+ else
49
+ RSpec::Support.require_rspec_support 'source'
50
+ def source
51
+ raise TargetNotFoundError unless File.exist?(file_path)
52
+ @source ||= RSpec::Support::Source.from_file(file_path)
53
+ end
54
+ end
55
+
56
+ def file_path
57
+ source_location.first
58
+ end
59
+
60
+ def beginning_line_number
61
+ source_location.last
62
+ end
63
+
64
+ def source_location
65
+ proc.source_location || raise(TargetNotFoundError)
66
+ end
67
+
68
+ Error = Class.new(StandardError)
69
+ TargetNotFoundError = Class.new(Error)
70
+ AmbiguousTargetError = Class.new(Error)
71
+
72
+ # @private
73
+ # Performs extraction of block body snippet using tokens,
74
+ # which cannot be done with node information.
75
+ BlockTokenExtractor = Struct.new(:method_name, :source, :beginning_line_number) do
76
+ attr_reader :state, :body_tokens
77
+
78
+ def initialize(*)
79
+ super
80
+ parse!
81
+ end
82
+
83
+ private
84
+
85
+ def parse!
86
+ @state = :initial
87
+
88
+ catch(:finish) do
89
+ source.tokens.each do |token|
90
+ invoke_state_handler(token)
91
+ end
92
+ end
93
+ end
94
+
95
+ def finish!
96
+ throw :finish
97
+ end
98
+
99
+ def invoke_state_handler(token)
100
+ __send__("#{state}_state", token)
101
+ end
102
+
103
+ def initial_state(token)
104
+ @state = :after_method_call if token.location == block_locator.method_call_location
105
+ end
106
+
107
+ def after_method_call_state(token)
108
+ @state = :after_opener if handle_opener_token(token)
109
+ end
110
+
111
+ def after_opener_state(token)
112
+ if handle_closer_token(token)
113
+ finish_or_find_next_block_if_incorrect!
114
+ elsif pipe_token?(token)
115
+ finalize_pending_tokens!
116
+ @state = :after_beginning_of_args
117
+ else
118
+ pending_tokens << token
119
+ handle_opener_token(token)
120
+ @state = :after_beginning_of_body unless token.type == :on_sp
121
+ end
122
+ end
123
+
124
+ def after_beginning_of_args_state(token)
125
+ @state = :after_beginning_of_body if pipe_token?(token)
126
+ end
127
+
128
+ def after_beginning_of_body_state(token)
129
+ if handle_closer_token(token)
130
+ finish_or_find_next_block_if_incorrect!
131
+ else
132
+ pending_tokens << token
133
+ handle_opener_token(token)
134
+ end
135
+ end
136
+
137
+ def pending_tokens
138
+ @pending_tokens ||= []
139
+ end
140
+
141
+ def finalize_pending_tokens!
142
+ pending_tokens.freeze.tap do
143
+ @pending_tokens = nil
144
+ end
145
+ end
146
+
147
+ def finish_or_find_next_block_if_incorrect!
148
+ body_tokens = finalize_pending_tokens!
149
+
150
+ if correct_block?(body_tokens)
151
+ @body_tokens = body_tokens
152
+ finish!
153
+ else
154
+ @state = :after_method_call
155
+ end
156
+ end
157
+
158
+ def handle_opener_token(token)
159
+ opener_token?(token).tap do |boolean|
160
+ opener_token_stack.push(token) if boolean
161
+ end
162
+ end
163
+
164
+ def opener_token?(token)
165
+ token.type == :on_lbrace || (token.type == :on_kw && token.string == 'do')
166
+ end
167
+
168
+ def handle_closer_token(token)
169
+ if opener_token_stack.last.closed_by?(token)
170
+ opener_token_stack.pop
171
+ opener_token_stack.empty?
172
+ else
173
+ false
174
+ end
175
+ end
176
+
177
+ def opener_token_stack
178
+ @opener_token_stack ||= []
179
+ end
180
+
181
+ def pipe_token?(token)
182
+ token.type == :on_op && token.string == '|'
183
+ end
184
+
185
+ def correct_block?(body_tokens)
186
+ return true if block_locator.body_content_locations.empty?
187
+ content_location = block_locator.body_content_locations.first
188
+ content_location.between?(body_tokens.first.location, body_tokens.last.location)
189
+ end
190
+
191
+ def block_locator
192
+ @block_locator ||= BlockLocator.new(method_name, source, beginning_line_number)
193
+ end
194
+ end
195
+
196
+ # @private
197
+ # Locates target block with node information (semantics), which tokens don't have.
198
+ BlockLocator = Struct.new(:method_name, :source, :beginning_line_number) do
199
+ def method_call_location
200
+ @method_call_location ||= method_ident_node.location
201
+ end
202
+
203
+ def body_content_locations
204
+ @body_content_locations ||= block_body_node.map(&:location).compact
205
+ end
206
+
207
+ private
208
+
209
+ def method_ident_node
210
+ method_call_node = block_wrapper_node.children.first
211
+ method_call_node.find do |node|
212
+ method_ident_node?(node)
213
+ end
214
+ end
215
+
216
+ def block_body_node
217
+ block_node = block_wrapper_node.children[1]
218
+ block_node.children.last
219
+ end
220
+
221
+ def block_wrapper_node
222
+ case candidate_block_wrapper_nodes.size
223
+ when 1
224
+ candidate_block_wrapper_nodes.first
225
+ when 0
226
+ raise TargetNotFoundError
227
+ else
228
+ raise AmbiguousTargetError
229
+ end
230
+ end
231
+
232
+ def candidate_block_wrapper_nodes
233
+ @candidate_block_wrapper_nodes ||= candidate_method_ident_nodes.map do |method_ident_node|
234
+ block_wrapper_node = method_ident_node.each_ancestor.find { |node| node.type == :method_add_block }
235
+ next nil unless block_wrapper_node
236
+ method_call_node = block_wrapper_node.children.first
237
+ method_call_node.include?(method_ident_node) ? block_wrapper_node : nil
238
+ end.compact
239
+ end
240
+
241
+ def candidate_method_ident_nodes
242
+ source.nodes_by_line_number[beginning_line_number].select do |node|
243
+ method_ident_node?(node)
244
+ end
245
+ end
246
+
247
+ def method_ident_node?(node)
248
+ node.type == :@ident && node.args.first == method_name
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end