Skip to content

Commit 02285d2

Browse files
committed
Fix nested pseudo pair case
The problem here is that we still have a remove_pseudo_pair situation two of these lines are valid when paired with above/below, one is not however when you look for the next `above` it shows `setup_language_pack_environment(` which is correct, but the below returns `bundle_default_without: "development:test"` which is technically correct, but not useful to us, we need that node's below The fix was to re-group the invalid nodes with the original above/below The downside of this approch is that it may violate expectations of above/below guarantees. The original document can no longer be re-created. It makes things very convenient though so this seems like the right path forward. It also seems that `invalid_inside_split_pair` and `remove_pseudo_pair` are essentially the same thing, but one has captured the leaning blocks inside of the node's parents while the other simply has them as an above/below reference. We might be able to simplify something later. I'm pleased with this result, it isolates exactly just the failing line. 192 examples, 4 failures, 1 pending Failed examples: rspec ./spec/integration/ruby_command_line_spec.rb:46 # Requires with ruby cli detects require error and adds a message with auto mode rspec ./spec/unit/indent_search_spec.rb:1034 # DeadEnd::IndentSearch doesn't scapegoat rescue rspec ./spec/unit/indent_tree_spec.rb:721 # DeadEnd::IndentTree finds random pipe (|) wildly misindented rspec ./spec/unit/indent_tree_spec.rb:1052 # DeadEnd::IndentTree syntax_tree.rb.txt for performance validation It seems like if the fix here was in diagnose, that I need to have a better set of diagnose tests. Considering there are none! It seems like it would be a good idea to start there next time. We can collect cases from the existing indent_tree_spec. Ultimately we need to assert the indent tree shape/properties rather than what we're currently doing with walking/diagnosing/searching in the name of testing. However there's a dependency resolution problem. We are testing a tree structure so we need an easy way to assert properties of that structure. But changes to the structure change it's properties. In short until we have a working solution the properties we desire won't be 100% clear.
1 parent be9b4c4 commit 02285d2

File tree

4 files changed

+65
-38
lines changed

4 files changed

+65
-38
lines changed

lib/dead_end/block_node.rb

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module DeadEnd
66
# A block node keeps a reference to the block above it
77
# and below it. In addition a block can "capture" another
88
# block. Block nodes are treated as immutable(ish) so when that happens
9-
# a new node is created that contains a refernce to all the blocks it was
9+
# a new node is created that contains a reference to all the blocks it was
1010
# derived from. These are known as a block's "parents".
1111
#
1212
# If you walk the parent chain until it ends you'll end up with nodes
@@ -33,7 +33,7 @@ class BlockNode
3333
#
3434
# block = BlockNode.from_blocks([parents[0], parents[2]])
3535
# expect(block.leaning).to eq(:equal)
36-
def self.from_blocks(parents)
36+
def self.from_blocks(parents, above: nil, below: nil)
3737
lines = []
3838
while parents.length == 1 && parents.first.parents.any?
3939
parents = parents.first.parents
@@ -47,8 +47,9 @@ def self.from_blocks(parents)
4747
block.delete
4848
end
4949

50-
above = parents.first.above
51-
below = parents.last.below
50+
above ||= parents.first.above
51+
below ||= parents.last.below
52+
5253

5354
parents = [] if parents.length == 1
5455

lib/dead_end/diagnose_node.rb

+28-30
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ module DeadEnd
1717
# The algorithm here is tightly coupled to the nodes produced by the current IndentTree
1818
# implementation.
1919
#
20-
#
2120
# Possible problem states:
2221
#
2322
# - :self - The block holds no parents, if it holds a problem its in the current node.
2423
#
2524
# - :invalid_inside_split_pair - An invalid block is splitting two valid leaning blocks, return the middle.
2625
#
2726
# - :remove_pseudo_pair - Multiple invalid blocks in isolation are present, but when paired with external leaning
28-
# blocks above and below they become valid. Remove these and group the leftovers together. i.e. `else/ensure/rescue`.
27+
# blocks above and below they become valid. Remove these and group the leftovers together. i.e. don't
28+
# scapegoat `else/ensure/rescue`, remove them from the block and retry with whats leftover.
2929
#
3030
# - :extract_from_multiple - Multiple invalid blocks in isolation are present, but we were able to find one that could be removed
3131
# to make a valid set along with outer leaning i.e. `[`, `in)&lid` , `vaild`, `]`. Different from :invalid_inside_split_pair because
@@ -65,7 +65,7 @@ def call
6565
@next = if @problem == :multiple_invalid_parents
6666
invalid.map { |b| BlockNode.from_blocks([b]) }
6767
else
68-
[BlockNode.from_blocks(invalid)]
68+
invalid
6969
end
7070

7171
self
@@ -86,32 +86,35 @@ def call
8686
diagnose_one_or_more_parents
8787
end
8888

89+
# Diagnose left/right
90+
#
91+
# Handles cases where the block is made up of a several nodes and is book ended by
92+
# nodes leaning in the correct direction that pair with one another. For example [`{`, `b@&[d`, `}`]
93+
#
94+
# This is different from above/below which also has matching blocks, but those are outside of the current
95+
# block array (they are above and below it respectively)
96+
#
8997
# ## (:invalid_inside_split_pair) Handle case where keyword/end (or any pair) is falsely reported as invalid in isolation but
9098
# holds a syntax error inside of it.
9199
#
92100
# Example:
93101
#
94102
# ```
95-
# def cow # left, invalid in isolation, valid when paired with end
96-
# ```
97-
#
98-
# ```
103+
# def cow # left, invalid in isolation, valid when paired with end
99104
# inv&li) code # Actual problem to be isolated
100-
# ```
101-
#
102-
# ```
103-
# end # right, invalid in isolation, valid when paired with def
105+
# end # right, invalid in isolation, valid when paired with def
104106
# ```
105107
private def diagnose_left_right
106108
invalid = block.parents.select(&:invalid?)
109+
return false if invalid.length < 3
107110

108111
left = invalid.detect { |block| block.leaning == :left }
109112
right = invalid.reverse_each.detect { |block| block.leaning == :right }
110113

111-
if left && right && invalid.length >= 3 && BlockNode.from_blocks([left, right]).valid?
114+
if left && right && BlockNode.from_blocks([left, right]).valid?
112115
@problem = :invalid_inside_split_pair
113116

114-
invalid.reject! { |x| x == left || x == right }
117+
invalid.reject! { |b| b == left || b == right }
115118

116119
# If the left/right was not mapped properly or we've accidentally got a :multiple_invalid_parents
117120
# we can get a false positive, double check the invalid lines fully capture the problem
@@ -130,16 +133,10 @@ def call
130133
# Example:
131134
#
132135
# ```
133-
# def cow # above
134-
# ```
135-
#
136-
# ```
136+
# def cow # above
137137
# print inv&li) # Actual problem
138138
# rescue => e # Invalid in isolation, valid when paired with above/below
139-
# ```
140-
#
141-
# ```
142-
# end # below
139+
# end # below
143140
# ```
144141
#
145142
# ## (:extract_from_multiple) Handle syntax seems fine in isolation, but not when combined with above/below leaning blocks
@@ -148,22 +145,16 @@ def call
148145
#
149146
# ```
150147
# [ # above
151-
# ```
152-
#
153-
# ```
154148
# missing_comma_not_okay
155149
# missing_comma_okay
156-
# ```
157-
#
158-
# ```
159150
# ] # below
160151
# ```
152+
#
161153
private def diagnose_above_below
162154
invalid = block.parents.select(&:invalid?)
163155

164156
above = block.above if block.above&.leaning == :left
165157
below = block.below if block.below&.leaning == :right
166-
167158
return false if above.nil? || below.nil?
168159

169160
if invalid.reject! { |block|
@@ -172,11 +163,17 @@ def call
172163
}
173164

174165
if invalid.any?
166+
# At this point invalid array was reduced and represents only
167+
# nodes that are invalid when paired with it's above/below
168+
# however, we may need to split the node apart again
175169
@problem = :remove_pseudo_pair
176-
invalid
177-
else
178170

171+
[BlockNode.from_blocks(invalid, above: above, below: below)]
172+
else
179173
invalid = block.parents.select(&:invalid?)
174+
175+
# If we can remove one node from many blocks to make the other blocks valid then, that
176+
# block must be the problem
180177
if (b = invalid.detect { |b| BlockNode.from_blocks([above, invalid - [b], below].flatten).valid? })
181178
@problem = :extract_from_multiple
182179
[b]
@@ -189,6 +186,7 @@ def call
189186
private def diagnose_one_or_more_parents
190187
invalid = block.parents.select(&:invalid?)
191188
@problem = if invalid.length > 1
189+
192190
:multiple_invalid_parents
193191
else
194192
:one_invalid_parent

lib/dead_end/journey.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module DeadEnd
88
# valid code from it's parent
99
#
1010
# node = tree.root
11-
# journey = Journe.new(node)
11+
# journey = Journey.new(node)
1212
# journey << Step.new(node.parents[0])
1313
# expect(journey.node).to eq(node.parents[0])
1414
#

spec/unit/indent_tree_spec.rb

+31-3
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ def compile
625625
setup_language_pack_environment(
626626
ruby_layer_path: File.expand_path("."),
627627
gem_layer_path: File.expand_path("."),
628-
bundle_path: "vendor/bundle", }
628+
bundle_path: "vendor/bundle", } # problem
629629
bundle_default_without: "development:test"
630630
)
631631
allow_git do
@@ -672,21 +672,49 @@ def compile
672672
expect(diagnose.problem).to eq(:one_invalid_parent)
673673
node = diagnose.next[0]
674674

675+
expect(node.to_s).to eq(<<~'EOM'.indent(4))
676+
setup_language_pack_environment(
677+
ruby_layer_path: File.expand_path("."),
678+
gem_layer_path: File.expand_path("."),
679+
bundle_path: "vendor/bundle", } # problem
680+
bundle_default_without: "development:test"
681+
)
682+
EOM
683+
675684
diagnose = DiagnoseNode.new(node).call
676685
expect(diagnose.problem).to eq(:invalid_inside_split_pair)
677686
node = diagnose.next[0]
678687

688+
expect(node.to_s).to eq(<<~'EOM'.indent(6))
689+
ruby_layer_path: File.expand_path("."),
690+
gem_layer_path: File.expand_path("."),
691+
bundle_path: "vendor/bundle", } # problem
692+
bundle_default_without: "development:test"
693+
EOM
694+
679695
diagnose = DiagnoseNode.new(node).call
696+
node = diagnose.next[0]
680697
expect(diagnose.problem).to eq(:remove_pseudo_pair)
681-
expect(node.parents.length).to eq(4)
698+
699+
expect(node.to_s).to eq(<<~'EOM'.indent(6))
700+
ruby_layer_path: File.expand_path("."),
701+
gem_layer_path: File.expand_path("."),
702+
bundle_path: "vendor/bundle", } # problem
703+
EOM
682704

683705
diagnose = DiagnoseNode.new(node).call
684706
node = diagnose.next[0]
707+
expect(diagnose.problem).to eq(:remove_pseudo_pair)
708+
709+
expect(node.to_s).to eq(<<~'EOM'.indent(6))
710+
bundle_path: "vendor/bundle", } # problem
711+
EOM
685712

686713
diagnose = DiagnoseNode.new(node).call
687714
expect(diagnose.problem).to eq(:self)
715+
688716
expect(node.to_s).to eq(<<~'EOM'.indent(6))
689-
bundle_path: "vendor/bundle", }
717+
bundle_path: "vendor/bundle", } # problem
690718
EOM
691719
end
692720

0 commit comments

Comments
 (0)