rspec-expectations 3.5.0 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|