dead_end 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7db29aed59a901a58a0b1ed50873b6f2c17692f7724ffad2736b99694b78ba0
4
- data.tar.gz: e1cf2a11fa38af85df30d89a559b0c6e5a74beec2c0add8b6f67dd142d793de1
3
+ metadata.gz: 9fb84957790492d9f453b8863ea276ffc603ccb365fee3621f322e3f19e172e5
4
+ data.tar.gz: 798626bcc0dfa8457ef1fed8fb9ab8c9783a12ee109d94310a0cf787b2a0491a
5
5
  SHA512:
6
- metadata.gz: 444cfdfd7df93038d1714729a21a3b7a20f9000e9cf0541521d51a125d00be4968e83bbc8a51bf4c4689e2b99706cf5eb0032a318647ecb3fd4452a343798e7a
7
- data.tar.gz: c532b87160ae6231776b72ff68f3c8f940064051e14c27da28fa22a15c1be0060d9b2afd600bb66dd4571cd39b9372b1956782565040a5e159776f9dac9f9e62
6
+ metadata.gz: 44624834e772d2c0d5c0035eb373571cb379cf6417a1d3422528f312c06771ef00c45e3c1ceebb8f78fa4ebce232fd99798f957c408a8a3a78710e65f18da7ce
7
+ data.tar.gz: 478fce76c26ffcf975111bb51a8ea71c20aa0ecbabb606d621d715e3d7055a818e82e5b1bc11cc7143e5cd4e120da257c1b33dc30a1b07356dacb782ad7299bf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 2.0.0
4
+
5
+ - Support "endless" oneline method definitions for Ruby 3+ (https://github.com/zombocom/dead_end/pull/80)
6
+ - Reduce timeout to 1 second (https://github.com/zombocom/dead_end/pull/79)
7
+ - Logically consecutive lines (such as chained methods are now joined) (https://github.com/zombocom/dead_end/pull/78)
8
+ - Output improvement for cases where the only line is an single `end` (https://github.com/zombocom/dead_end/pull/78)
9
+
3
10
  ## 1.2.0
4
11
 
5
12
  - Output improvements via less greedy unmatched kw capture https://github.com/zombocom/dead_end/pull/73
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (1.2.0)
4
+ dead_end (2.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -9,10 +9,10 @@ module DeadEnd
9
9
  #
10
10
  # Example:
11
11
  #
12
- # def dog
13
- # puts "bark"
14
- # puts "bark"
15
- # end
12
+ # def dog # 1
13
+ # puts "bark" # 2
14
+ # puts "bark" # 3
15
+ # end # 4
16
16
  #
17
17
  # scan = AroundBlockScan.new(
18
18
  # code_lines: code_lines
@@ -22,7 +22,7 @@ module DeadEnd
22
22
  # scan.scan_while { true }
23
23
  #
24
24
  # puts scan.before_index # => 0
25
- # puts scan.after_index # => 3
25
+ # puts scan.after_index # => 3
26
26
  #
27
27
  # Contents can also be filtered using AroundBlockScan#skip
28
28
  #
@@ -109,8 +109,6 @@ module DeadEnd
109
109
  kw_count = 0
110
110
  end_count = 0
111
111
  after_lines.each do |line|
112
- # puts "line: #{line.number} #{line.original_line}, indent: #{line.indent}, #{line.empty?} #{line.indent == @orig_indent}"
113
-
114
112
  next if line.empty?
115
113
  break if line.indent < @orig_indent
116
114
  next if line.indent != @orig_indent
@@ -124,7 +122,6 @@ module DeadEnd
124
122
 
125
123
  lines << line
126
124
  end
127
- lines.select! { |line| !line.is_comment? }
128
125
 
129
126
  lines
130
127
  end
@@ -1,13 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- # Given a block, this method will capture surrounding
5
- # code to give the user more context for the location of
6
- # the problem.
4
+ # Turns a "invalid block(s)" into useful context
7
5
  #
8
- # Return is an array of CodeLines to be rendered.
6
+ # There are three main phases in the algorithm:
9
7
  #
10
- # Surrounding code is captured regardless of visible state
8
+ # 1. Sanitize/format input source
9
+ # 2. Search for invalid blocks
10
+ # 3. Format invalid blocks into something meaninful
11
+ #
12
+ # This class handles the third part.
13
+ #
14
+ # The algorithm is very good at capturing all of a syntax
15
+ # error in a single block in number 2, however the results
16
+ # can contain ambiguities. Humans are good at pattern matching
17
+ # and filtering and can mentally remove extraneous data, but
18
+ # they can't add extra data that's not present.
19
+ #
20
+ # In the case of known ambiguious cases, this class adds context
21
+ # back to the ambiguitiy so the programmer has full information.
22
+ #
23
+ # Beyond handling these ambiguities, it also captures surrounding
24
+ # code context information:
11
25
  #
12
26
  # puts block.to_s # => "def bark"
13
27
  #
@@ -16,7 +30,8 @@ module DeadEnd
16
30
  # code_lines: code_lines
17
31
  # )
18
32
  #
19
- # puts context.call.join
33
+ # lines = context.call.map(&:original)
34
+ # puts lines.join
20
35
  # # =>
21
36
  # class Dog
22
37
  # def bark
@@ -34,19 +49,34 @@ module DeadEnd
34
49
 
35
50
  def call
36
51
  @blocks.each do |block|
52
+ capture_first_kw_end_same_indent(block)
37
53
  capture_last_end_same_indent(block)
38
54
  capture_before_after_kws(block)
39
55
  capture_falling_indent(block)
40
56
  end
41
57
 
42
58
  @lines_to_output.select!(&:not_empty?)
43
- @lines_to_output.select!(&:not_comment?)
44
59
  @lines_to_output.uniq!
45
60
  @lines_to_output.sort!
46
61
 
47
62
  @lines_to_output
48
63
  end
49
64
 
65
+ # Shows the context around code provided by "falling" indentation
66
+ #
67
+ # Converts:
68
+ #
69
+ # it "foo" do
70
+ #
71
+ # into:
72
+ #
73
+ # class OH
74
+ # def hello
75
+ # it "foo" do
76
+ # end
77
+ # end
78
+ #
79
+ #
50
80
  def capture_falling_indent(block)
51
81
  AroundBlockScan.new(
52
82
  block: block,
@@ -56,7 +86,36 @@ module DeadEnd
56
86
  end
57
87
  end
58
88
 
89
+ # Shows surrounding kw/end pairs
90
+ #
91
+ # The purpose of showing these extra pairs is due to cases
92
+ # of ambiguity when only one visible line is matched.
93
+ #
94
+ # For example:
95
+ #
96
+ # 1 class Dog
97
+ # 2 def bark
98
+ # 4 def eat
99
+ # 5 end
100
+ # 6 end
101
+ #
102
+ # In this case either line 2 could be missing an `end` or
103
+ # line 4 was an extra line added by mistake (it happens).
104
+ #
105
+ # When we detect the above problem it shows the issue
106
+ # as only being on line 2
107
+ #
108
+ # 2 def bark
109
+ #
110
+ # Showing "neighbor" keyword pairs gives extra context:
111
+ #
112
+ # 2 def bark
113
+ # 4 def eat
114
+ # 5 end
115
+ #
59
116
  def capture_before_after_kws(block)
117
+ return unless block.visible_lines.count == 1
118
+
60
119
  around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
61
120
  .start_at_next_line
62
121
  .capture_neighbor_context
@@ -66,9 +125,10 @@ module DeadEnd
66
125
  @lines_to_output.concat(around_lines)
67
126
  end
68
127
 
69
- # When there is an invalid with a keyword
70
- # right before an end, it's unclear where
71
- # the correct code should be.
128
+ # When there is an invalid block with a keyword
129
+ # missing an end right before another end,
130
+ # it is unclear where which keyword is missing the
131
+ # end
72
132
  #
73
133
  # Take this example:
74
134
  #
@@ -87,20 +147,21 @@ module DeadEnd
87
147
  # line 4. Also work backwards and if there's a mis-matched keyword, show it
88
148
  # too
89
149
  def capture_last_end_same_indent(block)
90
- start_index = block.visible_lines.first.index
91
- lines = @code_lines[start_index..block.lines.last.index]
150
+ return if block.visible_lines.length != 1
151
+ return unless block.visible_lines.first.is_kw?
152
+
153
+ visible_line = block.visible_lines.first
154
+ lines = @code_lines[visible_line.index..block.lines.last.index]
92
155
 
93
156
  # Find first end with same indent
94
157
  # (this would return line 4)
95
158
  #
96
159
  # end # 4
97
- matching_end = lines.find { |line| line.indent == block.current_indent && line.is_end? }
160
+ matching_end = lines.detect { |line| line.indent == block.current_indent && line.is_end? }
98
161
  return unless matching_end
99
162
 
100
163
  @lines_to_output << matching_end
101
164
 
102
- lines = @code_lines[start_index..matching_end.index]
103
-
104
165
  # Work backwards from the end to
105
166
  # see if there are mis-matched
106
167
  # keyword/end pairs
@@ -113,7 +174,7 @@ module DeadEnd
113
174
  # end # 4
114
175
  end_count = 0
115
176
  kw_count = 0
116
- kw_line = lines.reverse.detect do |line|
177
+ kw_line = @code_lines[visible_line.index..matching_end.index].reverse.detect do |line|
117
178
  end_count += 1 if line.is_end?
118
179
  kw_count += 1 if line.is_kw?
119
180
 
@@ -122,5 +183,51 @@ module DeadEnd
122
183
  return unless kw_line
123
184
  @lines_to_output << kw_line
124
185
  end
186
+
187
+ # The logical inverse of `capture_last_end_same_indent`
188
+ #
189
+ # When there is an invalid block with an `end`
190
+ # missing a keyword right after another `end`,
191
+ # it is unclear where which end is missing the
192
+ # keyword.
193
+ #
194
+ # Take this example:
195
+ #
196
+ # class Dog # 1
197
+ # puts "woof" # 2
198
+ # end # 3
199
+ # end # 4
200
+ #
201
+ # the problem line will be identified as:
202
+ #
203
+ # ❯ end # 4
204
+ #
205
+ # This happens because lines 1, 2, and 3 are technically valid code and are expanded
206
+ # first, deemed valid, and hidden. We need to un-hide the matching keyword on
207
+ # line 1. Also work backwards and if there's a mis-matched end, show it
208
+ # too
209
+ def capture_first_kw_end_same_indent(block)
210
+ return if block.visible_lines.length != 1
211
+ return unless block.visible_lines.first.is_end?
212
+
213
+ visible_line = block.visible_lines.first
214
+ lines = @code_lines[block.lines.first.index..visible_line.index]
215
+ matching_kw = lines.reverse.detect { |line| line.indent == block.current_indent && line.is_kw? }
216
+ return unless matching_kw
217
+
218
+ @lines_to_output << matching_kw
219
+
220
+ kw_count = 0
221
+ end_count = 0
222
+ orphan_end = @code_lines[matching_kw.index..visible_line.index].detect do |line|
223
+ kw_count += 1 if line.is_kw?
224
+ end_count += 1 if line.is_end?
225
+
226
+ end_count >= kw_count
227
+ end
228
+
229
+ return unless orphan_end
230
+ @lines_to_output << orphan_end
231
+ end
125
232
  end
126
233
  end
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Parses and sanitizes source into a lexically aware document
5
+ #
6
+ # Internally the document is represented by an array with each
7
+ # index containing a CodeLine correlating to a line from the source code.
8
+ #
9
+ # There are three main phases in the algorithm:
10
+ #
11
+ # 1. Sanitize/format input source
12
+ # 2. Search for invalid blocks
13
+ # 3. Format invalid blocks into something meaninful
14
+ #
15
+ # This class handles the first part.
16
+ #
17
+ # The reason this class exists is to format input source
18
+ # for better/easier/cleaner exploration.
19
+ #
20
+ # The CodeSearch class operates at the line level so
21
+ # we must be careful to not introduce lines that look
22
+ # valid by themselves, but when removed will trigger syntax errors
23
+ # or strange behavior.
24
+ #
25
+ # ## Join Trailing slashes
26
+ #
27
+ # Code with a trailing slash is logically treated as a single line:
28
+ #
29
+ # 1 it "code can be split" \
30
+ # 2 "across multiple lines" do
31
+ #
32
+ # In this case removing line 2 would add a syntax error. We get around
33
+ # this by internally joining the two lines into a single "line" object
34
+ #
35
+ # ## Logically Consecutive lines
36
+ #
37
+ # Code that can be broken over multiple
38
+ # lines such as method calls are on different lines:
39
+ #
40
+ # 1 User.
41
+ # 2 where(name: "schneems").
42
+ # 3 first
43
+ #
44
+ # Removing line 2 can introduce a syntax error. To fix this, all lines
45
+ # are joined into one.
46
+ #
47
+ # ## Heredocs
48
+ #
49
+ # A heredoc is an way of defining a multi-line string. They can cause many
50
+ # problems. If left as a single line, Ripper would try to parse the contents
51
+ # as ruby code rather than as a string. Even without this problem, we still
52
+ # hit an issue with indentation
53
+ #
54
+ # 1 foo = <<~HEREDOC
55
+ # 2 "Be yourself; everyone else is already taken.""
56
+ # 3 ― Oscar Wilde
57
+ # 4 puts "I look like ruby code" # but i'm still a heredoc
58
+ # 5 HEREDOC
59
+ #
60
+ # If we didn't join these lines then our algorithm would think that line 4
61
+ # is separate from the rest, has a higher indentation, then look at it first
62
+ # and remove it.
63
+ #
64
+ # If the code evaluates line 5 by itself it will think line 5 is a constant,
65
+ # remove it, and introduce a syntax errror.
66
+ #
67
+ # All of these problems are fixed by joining the whole heredoc into a single
68
+ # line.
69
+ #
70
+ # ## Comments and whitespace
71
+ #
72
+ # Comments can throw off the way the lexer tells us that the line
73
+ # logically belongs with the next line. This is valid ruby but
74
+ # results in a different lex output than before:
75
+ #
76
+ # 1 User.
77
+ # 2 where(name: "schneems").
78
+ # 3 # Comment here
79
+ # 4 first
80
+ #
81
+ # To handle this we can replace comment lines with empty lines
82
+ # and then re-lex the source. This removal and re-lexing preserves
83
+ # line index and document size, but generates an easier to work with
84
+ # document.
85
+ #
86
+ class CleanDocument
87
+ def initialize(source:)
88
+ @source = source
89
+ @document = CodeLine.from_source(@source)
90
+ end
91
+
92
+ # Call all of the document "cleaners"
93
+ # and return self
94
+ def call
95
+ clean_sweep
96
+ .join_trailing_slash!
97
+ .join_consecutive!
98
+ .join_heredoc!
99
+
100
+ self
101
+ end
102
+
103
+ # Return an array of CodeLines in the
104
+ # document
105
+ def lines
106
+ @document
107
+ end
108
+
109
+ # Renders the document back to a string
110
+ def to_s
111
+ @document.join
112
+ end
113
+
114
+ # Remove comments and whitespace only lines
115
+ #
116
+ # replace with empty newlines
117
+ #
118
+ # source = <<~'EOM'
119
+ # # Comment 1
120
+ # puts "hello"
121
+ # # Comment 2
122
+ # puts "world"
123
+ # EOM
124
+ #
125
+ # lines = CleanDocument.new(source: source).clean_sweep.lines
126
+ # expect(lines[0].to_s).to eq("\n")
127
+ # expect(lines[1].to_s).to eq("puts "hello")
128
+ # expect(lines[2].to_s).to eq("\n")
129
+ # expect(lines[3].to_s).to eq("puts "world")
130
+ #
131
+ # WARNING:
132
+ # If you run this after any of the "join" commands, they
133
+ # will be un-joined.
134
+ #
135
+ # After this change is made, we re-lex the document because
136
+ # removing comments can change how the doc is parsed.
137
+ #
138
+ # For example:
139
+ #
140
+ # values = LexAll.new(source: <<~EOM))
141
+ # User.
142
+ # # comment
143
+ # where(name: 'schneems')
144
+ # EOM
145
+ # expect(values.count {|v| v.type == :on_ignored_nl}).to eq(1)
146
+ #
147
+ # After the comment is removed:
148
+ #
149
+ # values = LexAll.new(source: <<~EOM))
150
+ # User.
151
+ #
152
+ # where(name: 'schneems')
153
+ # EOM
154
+ # expect(values.count {|v| v.type == :on_ignored_nl}).to eq(2)
155
+ #
156
+ def clean_sweep
157
+ source = @document.map do |code_line|
158
+ # Clean trailing whitespace on empty line
159
+ if code_line.line.strip.empty?
160
+ next CodeLine.new(line: "\n", index: code_line.index, lex: [])
161
+ end
162
+
163
+ # Remove comments
164
+ if code_line.lex.detect { |lex| lex.type != :on_sp }&.type == :on_comment
165
+ next CodeLine.new(line: "\n", index: code_line.index, lex: [])
166
+ end
167
+
168
+ code_line
169
+ end.join
170
+
171
+ @source = source
172
+ @document = CodeLine.from_source(source)
173
+ self
174
+ end
175
+
176
+ # Smushes all heredoc lines into one line
177
+ #
178
+ # source = <<~'EOM'
179
+ # foo = <<~HEREDOC
180
+ # lol
181
+ # hehehe
182
+ # HEREDOC
183
+ # EOM
184
+ #
185
+ # lines = CleanDocument.new(source: source).join_heredoc!.lines
186
+ # expect(lines[0].to_s).to eq(source)
187
+ # expect(lines[1].to_s).to eq("")
188
+ def join_heredoc!
189
+ start_index_stack = []
190
+ heredoc_beg_end_index = []
191
+ lines.each do |line|
192
+ line.lex.each do |lex_value|
193
+ case lex_value.type
194
+ when :on_heredoc_beg
195
+ start_index_stack << line.index
196
+ when :on_heredoc_end
197
+ start_index = start_index_stack.pop
198
+ end_index = line.index
199
+ heredoc_beg_end_index << [start_index, end_index]
200
+ end
201
+ end
202
+ end
203
+
204
+ heredoc_groups = heredoc_beg_end_index.map { |start_index, end_index| @document[start_index..end_index] }
205
+
206
+ join_groups(heredoc_groups)
207
+ self
208
+ end
209
+
210
+ # Smushes logically "consecutive" lines
211
+ #
212
+ # source = <<~'EOM'
213
+ # User.
214
+ # where(name: 'schneems').
215
+ # first
216
+ # EOM
217
+ #
218
+ # lines = CleanDocument.new(source: source).join_consecutive!.lines
219
+ # expect(lines[0].to_s).to eq(source)
220
+ # expect(lines[1].to_s).to eq("")
221
+ #
222
+ # The one known case this doesn't handle is:
223
+ #
224
+ # Ripper.lex <<~EOM
225
+ # a &&
226
+ # b ||
227
+ # c
228
+ # EOM
229
+ #
230
+ # For some reason this introduces `on_ignore_newline` but with BEG type
231
+ #
232
+ def join_consecutive!
233
+ consecutive_groups = @document.select(&:ignore_newline_not_beg?).map do |code_line|
234
+ take_while_including(code_line.index..) do |line|
235
+ line.ignore_newline_not_beg?
236
+ end
237
+ end
238
+
239
+ join_groups(consecutive_groups)
240
+ self
241
+ end
242
+
243
+ # Join lines with a trailing slash
244
+ #
245
+ # source = <<~'EOM'
246
+ # it "code can be split" \
247
+ # "across multiple lines" do
248
+ # EOM
249
+ #
250
+ # lines = CleanDocument.new(source: source).join_consecutive!.lines
251
+ # expect(lines[0].to_s).to eq(source)
252
+ # expect(lines[1].to_s).to eq("")
253
+ def join_trailing_slash!
254
+ trailing_groups = @document.select(&:trailing_slash?).map do |code_line|
255
+ take_while_including(code_line.index..) { |x| x.trailing_slash? }
256
+ end
257
+ join_groups(trailing_groups)
258
+ self
259
+ end
260
+
261
+ # Helper method for joining "groups" of lines
262
+ #
263
+ # Input is expected to be type Array<Array<CodeLine>>
264
+ #
265
+ # The outer array holds the various "groups" while the
266
+ # inner array holds code lines.
267
+ #
268
+ # All code lines are "joined" into the first line in
269
+ # their group.
270
+ #
271
+ # To preserve document size, empty lines are placed
272
+ # in the place of the lines that were "joined"
273
+ def join_groups(groups)
274
+ groups.each do |lines|
275
+ line = lines.first
276
+
277
+ # Handle the case of multiple groups in a a row
278
+ # if one is already replaced, move on
279
+ next if @document[line.index].empty?
280
+
281
+ # Join group into the first line
282
+ @document[line.index] = CodeLine.new(
283
+ lex: lines.map(&:lex).flatten,
284
+ line: lines.join,
285
+ index: line.index
286
+ )
287
+
288
+ # Hide the rest of the lines
289
+ lines[1..].each do |line|
290
+ # The above lines already have newlines in them, if add more
291
+ # then there will be double newline, use an empty line instead
292
+ @document[line.index] = CodeLine.new(line: "", index: line.index, lex: [])
293
+ end
294
+ end
295
+ self
296
+ end
297
+
298
+ # Helper method for grabbing elements from document
299
+ #
300
+ # Like `take_while` except when it stops
301
+ # iterating, it also returns the line
302
+ # that caused it to stop
303
+ def take_while_including(range = 0..)
304
+ take_next_and_stop = false
305
+ @document[range].take_while do |line|
306
+ next if take_next_and_stop
307
+
308
+ take_next_and_stop = !(yield line)
309
+ true
310
+ end
311
+ end
312
+ end
313
+ end
@@ -3,11 +3,19 @@
3
3
  module DeadEnd
4
4
  # The main function of the frontier is to hold the edges of our search and to
5
5
  # evaluate when we can stop searching.
6
+
7
+ # There are three main phases in the algorithm:
8
+ #
9
+ # 1. Sanitize/format input source
10
+ # 2. Search for invalid blocks
11
+ # 3. Format invalid blocks into something meaninful
12
+ #
13
+ # The Code frontier is a critical part of the second step
6
14
  #
7
15
  # ## Knowing where we've been
8
16
  #
9
- # Once a code block is generated it is added onto the frontier where it will be
10
- # sorted and then the frontier can be filtered. Large blocks that totally contain a
17
+ # Once a code block is generated it is added onto the frontier. Then it will be
18
+ # sorted by indentation and frontier can be filtered. Large blocks that fully enclose a
11
19
  # smaller block will cause the smaller block to be evicted.
12
20
  #
13
21
  # CodeFrontier#<<(block) # Adds block to frontier
@@ -15,11 +23,11 @@ module DeadEnd
15
23
  #
16
24
  # ## Knowing where we can go
17
25
  #
18
- # Internally it keeps track of "unvisited" lines which is exposed via `next_indent_line`
19
- # when called this will return a line of code with the most indentation.
26
+ # Internally the frontier keeps track of "unvisited" lines which are exposed via `next_indent_line`
27
+ # when called, this method returns, a line of code with the highest indentation.
20
28
  #
21
- # This line of code can be used to build a CodeBlock and then when that code block
22
- # is added back to the frontier, then the lines are removed from the
29
+ # The returned line of code can be used to build a CodeBlock and then that code block
30
+ # is added back to the frontier. Then, the lines are removed from the
23
31
  # "unvisited" so we don't double-create the same block.
24
32
  #
25
33
  # CodeFrontier#next_indent_line # Shows next line
@@ -27,17 +35,20 @@ module DeadEnd
27
35
  #
28
36
  # ## Knowing when to stop
29
37
  #
30
- # The frontier holds the syntax error when removing all code blocks from the original
31
- # source document allows it to be parsed as syntatically valid:
38
+ # The frontier knows how to check the entire document for a syntax error. When blocks
39
+ # are added onto the frontier, they're removed from the document. When all code containing
40
+ # syntax errors has been added to the frontier, the document will be parsable without a
41
+ # syntax error and the search can stop.
32
42
  #
33
- # CodeFrontier#holds_all_syntax_errors?
43
+ # CodeFrontier#holds_all_syntax_errors? # Returns true when frontier holds all syntax errors
34
44
  #
35
45
  # ## Filtering false positives
36
46
  #
37
- # Once the search is completed, the frontier will have many blocks that do not contain
38
- # the syntax error. To filter to the smallest subset that does call:
47
+ # Once the search is completed, the frontier may have multiple blocks that do not contain
48
+ # the syntax error. To limit the result to the smallest subset of "invalid blocks" call:
39
49
  #
40
50
  # CodeFrontier#detect_invalid_blocks
51
+ #
41
52
  class CodeFrontier
42
53
  def initialize(code_lines:)
43
54
  @code_lines = code_lines
@@ -84,8 +95,8 @@ module DeadEnd
84
95
  puts "```"
85
96
  puts @frontier.last.to_s
86
97
  puts "```"
87
- puts " @frontier indent: #{frontier_indent}"
88
- puts " @unvisited indent: #{unvisited_indent}"
98
+ puts " @frontier indent: #{frontier_indent}"
99
+ puts " @unvisited indent: #{unvisited_indent}"
89
100
  end
90
101
 
91
102
  # Expand all blocks before moving to unvisited lines