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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +138 -2
- data/README.md +37 -20
- data/lib/rspec/expectations.rb +2 -1
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +14 -0
- data/lib/rspec/expectations/expectation_target.rb +2 -2
- data/lib/rspec/expectations/fail_with.rb +9 -1
- data/lib/rspec/expectations/handler.rb +2 -2
- data/lib/rspec/expectations/minitest_integration.rb +1 -1
- data/lib/rspec/expectations/syntax.rb +2 -2
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers.rb +97 -97
- data/lib/rspec/matchers/built_in/all.rb +1 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
- data/lib/rspec/matchers/built_in/be.rb +2 -2
- data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
- data/lib/rspec/matchers/built_in/change.rb +127 -53
- data/lib/rspec/matchers/built_in/compound.rb +6 -2
- data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
- data/lib/rspec/matchers/built_in/exist.rb +5 -1
- data/lib/rspec/matchers/built_in/has.rb +1 -1
- data/lib/rspec/matchers/built_in/include.rb +6 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
- data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
- data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
- data/lib/rspec/matchers/built_in/yield.rb +43 -30
- data/lib/rspec/matchers/composable.rb +6 -20
- data/lib/rspec/matchers/dsl.rb +72 -4
- data/lib/rspec/matchers/english_phrasing.rb +3 -3
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
- data/lib/rspec/matchers/generated_descriptions.rb +1 -2
- metadata +22 -18
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d8cb915ab51f5b587fcbaa864e697cc291ff3c1f49afd8d829837c1a9c4c4f6b
|
4
|
+
data.tar.gz: 6aa770883cacbfcf2921074a98506a0a789b4557ee6a7ea79738f008107e582d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc2669ca822767874cc62c9e7cef32040f90da7cd0d4c13b1bd97fde68230f7f7019a8b717e2ebfcd223da5b9e25aaa60270c0797ba4d304def5e4ae3bb63f03
|
7
|
+
data.tar.gz: e9d7a9e1b186c1eee547c315f177181992f5be9e35c61a16a041d97901537d054943a779e0d6bd7f2502e51a1fcbf95f0f6a10eab46fcdd85bebd8be1170a369
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/Changelog.md
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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 => "
|
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(
|
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
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
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
|
189
|
-
expect([1, 2, 3]).to include(1,
|
190
|
-
expect([1, 2, 3]).to
|
191
|
-
expect([1, 2, 3]).to start_with(1,
|
192
|
-
expect([1, 2, 3]).to
|
193
|
-
expect([1, 2, 3]).to end_with(
|
194
|
-
expect(
|
195
|
-
expect(
|
196
|
-
expect("this string").to
|
197
|
-
expect("this string").to
|
198
|
-
expect(
|
199
|
-
expect([1, 2, 3]).to
|
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(
|
283
|
+
an_instance_of(Integer)
|
267
284
|
),
|
268
285
|
:c => { :d => (a_value < 3) }
|
269
286
|
}
|
data/lib/rspec/expectations.rb
CHANGED
@@ -76,6 +76,7 @@ module RSpec
|
|
76
76
|
class MultipleExpectationsNotMetError < ExpectationNotMetError
|
77
77
|
end
|
78
78
|
|
79
|
-
autoload :
|
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
|