Skip to content

Commit 4b8bdf3

Browse files
committed
[close #32] Improve trailing end results
When the last keyword in a given logical "block" is missing the `end` then it's unclear if the missing end came from the surrounding block, or from the last keyword. Example: ```ruby class Dog def bark puts "woof" end ``` Before this patch the output of this code will be: ``` ❯ 1 class Dog `` With this patch the output of this code will be: ``` ❯ 1 class Dog ❯ 2 def bark ❯ 4 end ``` There's some key realizations to get to this point. First, while the output of the "before" case only looks like it's one line, it's actually a much larger block that holds down to line 4 but everything else is hidden. Second: We can detect for this scenario using lexing but starting at the last end of the problem block and working backwards to find a mismatched keyword.
1 parent 350253e commit 4b8bdf3

File tree

3 files changed

+122
-15
lines changed

3 files changed

+122
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## HEAD (unreleased)
22

33
- Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
4+
- Better results when missing "end" comes at the end of a capturing block (such as a class or module definition) (https://github.com/zombocom/dead_end/issues/32)
45

56
## 1.0.1
67

lib/dead_end/capture_code_context.rb

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,9 @@ def initialize(blocks: , code_lines:)
3535

3636
def call
3737
@blocks.each do |block|
38-
around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
39-
.start_at_next_line
40-
.capture_neighbor_context
41-
42-
around_lines -= block.lines
43-
44-
@lines_to_output.concat(around_lines)
45-
46-
AroundBlockScan.new(
47-
block: block,
48-
code_lines: @code_lines,
49-
).on_falling_indent do |line|
50-
@lines_to_output << line
51-
end
38+
capture_last_end_same_indent(block)
39+
capture_before_after_kws(block)
40+
capture_falling_indent(block)
5241
end
5342

5443
@lines_to_output.select!(&:not_empty?)
@@ -58,5 +47,70 @@ def call
5847

5948
return @lines_to_output
6049
end
50+
51+
def capture_falling_indent(block)
52+
AroundBlockScan.new(
53+
block: block,
54+
code_lines: @code_lines,
55+
).on_falling_indent do |line|
56+
@lines_to_output << line
57+
end
58+
end
59+
60+
def capture_before_after_kws(block)
61+
around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
62+
.start_at_next_line
63+
.capture_neighbor_context
64+
65+
around_lines -= block.lines
66+
67+
@lines_to_output.concat(around_lines)
68+
end
69+
70+
# Problems heredocs are back in play
71+
def capture_last_end_same_indent(block)
72+
start_index = block.visible_lines.first.index
73+
lines = @code_lines[start_index..block.lines.last.index]
74+
kw_end_lines = lines.select {|line| line.indent == block.current_indent && (line.is_end? || line.is_kw?) }
75+
76+
77+
# TODO handle case of heredocs showing up here
78+
#
79+
# Due to https://github.com/zombocom/dead_end/issues/32
80+
# There's a special case where a keyword right before the last
81+
# end of a valid block accidentally ends up identifying that the problem
82+
# was with the block instead of before it. To handle that
83+
# special case, we can re-parse back through the internals of blocks
84+
# and if they have mis-matched keywords and ends show the last one
85+
end_lines = kw_end_lines.select(&:is_end?)
86+
end_lines.each_with_index do |end_line, i|
87+
start_index = i.zero? ? 0 : end_lines[i-1].index
88+
end_index = end_line.index - 1
89+
lines = @code_lines[start_index..end_index]
90+
91+
stop_next = false
92+
kw_count = 0
93+
end_count = 0
94+
lines = lines.reverse.take_while do |line|
95+
next false if stop_next
96+
97+
end_count += 1 if line.is_end?
98+
kw_count += 1 if line.is_kw?
99+
100+
stop_next = true if !kw_count.zero? && kw_count >= end_count
101+
true
102+
end.reverse
103+
104+
next unless kw_count > end_count
105+
106+
lines = lines.select {|line| line.is_kw? || line.is_end? }
107+
108+
next if lines.empty?
109+
110+
@lines_to_output << end_line
111+
@lines_to_output << lines.first
112+
@lines_to_output << lines.last
113+
end
114+
end
61115
end
62116
end

spec/unit/capture_code_context_spec.rb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,58 @@
44

55
module DeadEnd
66
RSpec.describe CaptureCodeContext do
7+
it "shows ends of captured block" do
8+
lines = fixtures_dir.join("rexe.rb.txt").read.lines
9+
lines.delete_at(148 - 1)
10+
source = lines.join
11+
12+
search = CodeSearch.new(source)
13+
search.call
14+
15+
# expect(search.invalid_blocks.join.strip).to eq('class Dog')
16+
display = CaptureCodeContext.new(
17+
blocks: search.invalid_blocks,
18+
code_lines: search.code_lines
19+
)
20+
lines = display.call
21+
22+
lines = lines.sort.map(&:original)
23+
expect(lines.join).to eq(<<~EOM)
24+
class Rexe
25+
VERSION = '1.5.1'
26+
PROJECT_URL = 'https://github.com/keithrbennett/rexe'
27+
class Lookups
28+
def format_requires
29+
end
30+
class CommandLineParser
31+
end
32+
end
33+
EOM
34+
end
35+
36+
it "shows ends of captured block" do
37+
source = <<~'EOM'
38+
class Dog
39+
def bark
40+
puts "woof"
41+
end
42+
EOM
43+
search = CodeSearch.new(source)
44+
search.call
45+
46+
expect(search.invalid_blocks.join.strip).to eq('class Dog')
47+
display = CaptureCodeContext.new(
48+
blocks: search.invalid_blocks,
49+
code_lines: search.code_lines
50+
)
51+
lines = display.call.sort.map(&:original)
52+
expect(lines.join).to eq(<<~EOM)
53+
class Dog
54+
def bark
55+
end
56+
EOM
57+
end
58+
759
it "captures surrounding context on falling indent" do
860
syntax_string = <<~EOM
961
class Blerg
@@ -29,7 +81,7 @@ class Zerg
2981
blocks: search.invalid_blocks,
3082
code_lines: search.code_lines
3183
)
32-
lines = display.call.sort
84+
lines = display.call.sort.map(&:original)
3385
expect(lines.join).to eq(<<~EOM)
3486
class OH
3587
def hello

0 commit comments

Comments
 (0)