dead_end 1.0.0 → 1.1.2

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: f04f0b2618c6787e30d722b387fe19eb026923d588abf15d676566a3496970f7
4
- data.tar.gz: 9ea352efa6c3622eb9097a33f79db08219e4d156ea5ccbcfab3777e8e176472e
3
+ metadata.gz: 83ee512040b172db6ab10b5bc6220e856734653bb4c033291a5b2d6210485996
4
+ data.tar.gz: b0812e0b2f605101c0bdef7f5d08886d404e5c02f4e28bb11e982e6cabef4636
5
5
  SHA512:
6
- metadata.gz: 1dd513cdeba2f2e560eacd2113660e77f3eea9c0f0947629fe5c0e7352d2fcbf2178107fead38d7e7a32ead25e803b324809863af151eff70fb9831ffd43c8ec
7
- data.tar.gz: e377f56853a8fd6e72afd9bb262965876dfa0242158a7a7327523825a3e1c008173bf1c2d6b1bf25b5a79c51711326e6cae098cfe4b363407929ccbf7ae8ad78
6
+ metadata.gz: 69cd9208b68cd671f90fe82ef1cf69e2f7037286360665b648690d51e0e9206f4e53abe3c4da755c2fcec08ed76261ca85a7e17c2dfcef7589880d149c33daf3
7
+ data.tar.gz: b78698db009e17b234da6aa1cbc185b9c5d391e9466685e823911ed2f725b02c0e2d2d14a79fed02a3e907cbd84d96314fb335ea80aa6cc78adf2d1ba8fff4b8
@@ -1,5 +1,27 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.1.2
4
+
5
+ - Namespace Kernel method aliases (https://github.com/zombocom/dead_end/pull/51)
6
+
7
+ ## 1.1.1
8
+
9
+ - Safer NoMethodError annotation (https://github.com/zombocom/dead_end/pull/48)
10
+
11
+ ## 1.1.0
12
+
13
+ - Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
14
+ - Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
15
+
16
+ ## 1.0.2
17
+
18
+ - Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
19
+ - 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)
20
+
21
+ ## 1.0.1
22
+
23
+ - Fix performance issue when evaluating multiple block combinations (https://github.com/zombocom/dead_end/pull/35)
24
+
3
25
  ## 1.0.0
4
26
 
5
27
  - Gem name changed from `syntax_search` to `dead_end` (https://github.com/zombocom/syntax_search/pull/30)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (1.0.0)
4
+ dead_end (1.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -33,4 +33,4 @@ DEPENDENCIES
33
33
  stackprof
34
34
 
35
35
  BUNDLED WITH
36
- 2.1.4
36
+ 2.2.3
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DeadEnd
2
2
 
3
- An AI powered library to find syntax errors in your source code:
3
+ An error in your code forces you to stop. DeadEnd helps you find those errors to get you back on your way faster.
4
4
 
5
5
  DeadEnd: Unmatched `end` detected
6
6
 
@@ -18,7 +18,7 @@ An AI powered library to find syntax errors in your source code:
18
18
 
19
19
  ## Installation in your codebase
20
20
 
21
- To automatically search syntax errors when they happen, add this to your Gemfile:
21
+ To automatically annotate errors when they happen, add this to your Gemfile:
22
22
 
23
23
  ```ruby
24
24
  gem 'dead_end'
@@ -44,7 +44,7 @@ If you're using rspec add this to your `.rspec` file:
44
44
 
45
45
  ## Install the CLI
46
46
 
47
- To get the CLI and manually search for syntax errors, install the gem:
47
+ To get the CLI and manually search for syntax errors (but not automatically annotate them), you can manually install the gem:
48
48
 
49
49
  $ gem install dead_end
50
50
 
@@ -54,7 +54,7 @@ This gives you the CLI command `$ dead_end` for more info run `$ dead_end --help
54
54
 
55
55
  - Missing `end`:
56
56
 
57
- ```
57
+ ```ruby
58
58
  class Dog
59
59
  def bark
60
60
  puts "bark"
@@ -68,7 +68,7 @@ end
68
68
 
69
69
  - Unexpected `end`
70
70
 
71
- ```
71
+ ```ruby
72
72
  class Dog
73
73
  def speak
74
74
  @sounds.each |sound| # Note the missing `do` here
@@ -81,6 +81,21 @@ end
81
81
 
82
82
  As well as unmatched `|` and unmatched `}`. These errors can be time consuming to debug because Ruby often only tells you the last line in the file. The command `ruby -wc path/to/file.rb` can narrow it down a little bit, but this library does a better job.
83
83
 
84
+ ## What other errors does it handle?
85
+
86
+ In addition to syntax errors, the NoMethodError is annotated to show the line where the error occured, and the surrounding context:
87
+
88
+ ```
89
+ scratch.rb:7:in `call': undefined method `upcase' for nil:NilClass (NoMethodError)
90
+
91
+
92
+ 1 class Pet
93
+ 6 def call
94
+ ❯ 7 puts "Come here #{@neam.upcase}"
95
+ 8 end
96
+ 9 end
97
+ ```
98
+
84
99
  ## Sounds cool, but why isn't this baked into Ruby directly?
85
100
 
86
101
  I would love to get something like this directly in Ruby, but I first need to prove it's useful. The `did_you_mean` functionality started as a gem that was eventually adopted by a bunch of people and then Ruby core liked it enough that they included it in the source. The goal of this gem is to:
@@ -90,7 +105,7 @@ I would love to get something like this directly in Ruby, but I first need to pr
90
105
 
91
106
  ## Artificial Inteligence?
92
107
 
93
- This library uses a goal-seeking algorithm similar to that of a path-finding search. For more information [read the blog post about how it works under the hood](https://schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/).
108
+ This library uses a goal-seeking algorithm for syntax error detection similar to that of a path-finding search. For more information [read the blog post about how it works under the hood](https://schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/).
94
109
 
95
110
  ## How does it detect syntax error locations?
96
111
 
@@ -153,11 +153,20 @@ module DeadEnd
153
153
  self.scan_while {|line| line.not_empty? && line.indent >= @orig_indent }
154
154
  end
155
155
 
156
+ def next_up
157
+ @code_lines[before_index.pred]
158
+ end
159
+
160
+ def next_down
161
+ @code_lines[after_index.next]
162
+ end
163
+
156
164
  def scan_adjacent_indent
157
- before_indent = @code_lines[@orig_before_index.pred]&.indent || 0
158
- after_indent = @code_lines[@orig_after_index.next]&.indent || 0
165
+ before_after_indent = []
166
+ before_after_indent << (next_up&.indent || 0)
167
+ before_after_indent << (next_down&.indent || 0)
159
168
 
160
- indent = [before_indent, after_indent].min
169
+ indent = before_after_indent.min
161
170
  self.scan_while {|line| line.not_empty? && line.indent >= indent }
162
171
 
163
172
  self
@@ -183,11 +192,11 @@ module DeadEnd
183
192
  end
184
193
 
185
194
  private def before_lines
186
- @code_lines[0...@orig_before_index]
195
+ @code_lines[0...@orig_before_index] || []
187
196
  end
188
197
 
189
198
  private def after_lines
190
- @code_lines[@orig_after_index.next..-1]
199
+ @code_lines[after_index.next..-1] || []
191
200
  end
192
201
  end
193
202
  end
@@ -1,29 +1,31 @@
1
+ # frozen_string_literal: true
2
+ #
1
3
  require_relative "../dead_end/internals"
2
4
 
3
5
  # Monkey patch kernel to ensure that all `require` calls call the same
4
6
  # method
5
7
  module Kernel
6
- alias_method :original_require, :require
7
- alias_method :original_require_relative, :require_relative
8
- alias_method :original_load, :load
8
+ alias_method :dead_end_original_require, :require
9
+ alias_method :dead_end_original_require_relative, :require_relative
10
+ alias_method :dead_end_original_load, :load
9
11
 
10
12
  def load(file, wrap = false)
11
- original_load(file)
13
+ dead_end_original_load(file)
12
14
  rescue SyntaxError => e
13
15
  DeadEnd.handle_error(e)
14
16
  end
15
17
 
16
18
  def require(file)
17
- original_require(file)
19
+ dead_end_original_require(file)
18
20
  rescue SyntaxError => e
19
21
  DeadEnd.handle_error(e)
20
22
  end
21
23
 
22
24
  def require_relative(file)
23
25
  if Pathname.new(file).absolute?
24
- original_require file
26
+ dead_end_original_require file
25
27
  else
26
- original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
28
+ dead_end_original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
27
29
  end
28
30
  rescue SyntaxError => e
29
31
  DeadEnd.handle_error(e)
@@ -49,3 +51,52 @@ class Object
49
51
  end
50
52
  end
51
53
 
54
+ module DeadEnd
55
+ IsProduction = -> {
56
+ ENV["RAILS_ENV"] == "production" || ENV["RACK_ENV"] == "production"
57
+ }
58
+ end
59
+
60
+ # Unlike a syntax error, a NoMethodError can occur hundreds or thousands of times and
61
+ # chew up CPU and other resources. Since this is primarilly a "development" optimization
62
+ # we can attempt to disable this behavior in a production context.
63
+ if !DeadEnd::IsProduction.call
64
+ class NoMethodError
65
+ alias :dead_end_original_to_s :to_s
66
+
67
+ def to_s
68
+ return super if DeadEnd::IsProduction.call
69
+
70
+ file, line, _ = backtrace[0].split(":")
71
+ return super if !File.exist?(file)
72
+
73
+ index = line.to_i - 1
74
+ source = File.read(file)
75
+ code_lines = DeadEnd::CodeLine.parse(source)
76
+
77
+ block = DeadEnd::CodeBlock.new(lines: code_lines[index])
78
+ lines = DeadEnd::CaptureCodeContext.new(
79
+ blocks: block,
80
+ code_lines: code_lines
81
+ ).call
82
+
83
+ message = super.dup
84
+ message << $/
85
+ message << $/
86
+
87
+ message << DeadEnd::DisplayCodeWithLineNumbers.new(
88
+ lines: lines,
89
+ highlight_lines: block.lines,
90
+ terminal: self.class.to_tty?
91
+ ).call
92
+
93
+ message << $/
94
+ message
95
+ rescue => e
96
+ puts "DeadEnd Internal error: #{e.dead_end_original_to_s}"
97
+ puts "DeadEnd Internal backtrace:"
98
+ puts backtrace.map {|l| " " + l }.join($/)
99
+ super
100
+ end
101
+ end
102
+ end
@@ -35,20 +35,9 @@ module DeadEnd
35
35
 
36
36
  def call
37
37
  @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)
52
41
  end
53
42
 
54
43
  @lines_to_output.select!(&:not_empty?)
@@ -58,5 +47,70 @@ module DeadEnd
58
47
 
59
48
  return @lines_to_output
60
49
  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
61
115
  end
62
116
  end
@@ -17,10 +17,12 @@ module DeadEnd
17
17
  #
18
18
  #
19
19
  class CodeBlock
20
+ UNSET = Object.new.freeze
20
21
  attr_reader :lines
21
22
 
22
23
  def initialize(lines: [])
23
24
  @lines = Array(lines)
25
+ @valid = UNSET
24
26
  end
25
27
 
26
28
  def visible_lines
@@ -68,7 +70,8 @@ module DeadEnd
68
70
  end
69
71
 
70
72
  def valid?
71
- DeadEnd.valid?(self.to_s)
73
+ return @valid if @valid != UNSET
74
+ @valid = DeadEnd.valid?(self.to_s)
72
75
  end
73
76
 
74
77
  def to_s
@@ -10,20 +10,20 @@ module DeadEnd
10
10
  # sorted and then the frontier can be filtered. Large blocks that totally contain a
11
11
  # smaller block will cause the smaller block to be evicted.
12
12
  #
13
- # CodeFrontier#<<
14
- # CodeFrontier#pop
13
+ # CodeFrontier#<<(block) # Adds block to frontier
14
+ # CodeFrontier#pop # Removes block from frontier
15
15
  #
16
16
  # ## Knowing where we can go
17
17
  #
18
- # Internally it keeps track of an "indent hash" which is exposed via `next_indent_line`
18
+ # Internally it keeps track of "unvisited" lines which is exposed via `next_indent_line`
19
19
  # when called this will return a line of code with the most indentation.
20
20
  #
21
- # This line of code can be used to build a CodeBlock via and then when that code block
22
- # is added back to the frontier, then the lines in the code block are removed from the
23
- # indent hash so we don't double-create the same block.
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
23
+ # "unvisited" so we don't double-create the same block.
24
24
  #
25
- # CodeFrontier#next_indent_line
26
- # CodeFrontier#register_indent_block
25
+ # CodeFrontier#next_indent_line # Shows next line
26
+ # CodeFrontier#register_indent_block(block) # Removes lines from unvisited
27
27
  #
28
28
  # ## Knowing when to stop
29
29
  #
@@ -42,13 +42,7 @@ module DeadEnd
42
42
  def initialize(code_lines: )
43
43
  @code_lines = code_lines
44
44
  @frontier = []
45
- @indent_hash = {}
46
- code_lines.each do |line|
47
- next if line.empty?
48
-
49
- @indent_hash[line.indent] ||= []
50
- @indent_hash[line.indent] << line
51
- end
45
+ @unvisited_lines = @code_lines.sort_by(&:indent_index)
52
46
  end
53
47
 
54
48
  def count
@@ -75,38 +69,31 @@ module DeadEnd
75
69
  return @frontier.pop
76
70
  end
77
71
 
78
- def indent_hash_indent
79
- @indent_hash.keys.sort.last
80
- end
81
-
82
72
  def next_indent_line
83
- indent = @indent_hash.keys.sort.last
84
- @indent_hash[indent]&.first
73
+ @unvisited_lines.last
85
74
  end
86
75
 
87
76
  def expand?
88
77
  return false if @frontier.empty?
89
- return true if @indent_hash.empty?
78
+ return true if @unvisited_lines.empty?
90
79
 
91
80
  frontier_indent = @frontier.last.current_indent
92
- hash_indent = @indent_hash.keys.sort.last
81
+ unvisited_indent= next_indent_line.indent
93
82
 
94
83
  if ENV["DEBUG"]
95
84
  puts "```"
96
85
  puts @frontier.last.to_s
97
86
  puts "```"
98
87
  puts " @frontier indent: #{frontier_indent}"
99
- puts " @hash indent: #{hash_indent}"
88
+ puts " @unvisited indent: #{unvisited_indent}"
100
89
  end
101
90
 
102
- frontier_indent >= hash_indent
91
+ # Expand all blocks before moving to unvisited lines
92
+ frontier_indent >= unvisited_indent
103
93
  end
104
94
 
105
95
  def register_indent_block(block)
106
- block.lines.each do |line|
107
- @indent_hash[line.indent]&.delete(line)
108
- end
109
- @indent_hash.select! {|k, v| !v.empty?}
96
+ @unvisited_lines -= block.lines
110
97
  self
111
98
  end
112
99
 
@@ -143,7 +130,7 @@ module DeadEnd
143
130
  # Given that we know our syntax error exists somewhere in our frontier, we want to find
144
131
  # the smallest possible set of blocks that contain all the syntax errors
145
132
  def detect_invalid_blocks
146
- self.class.combination(@frontier).detect do |block_array|
133
+ self.class.combination(@frontier.select(&:invalid?)).detect do |block_array|
147
134
  holds_all_syntax_errors?(block_array)
148
135
  end || []
149
136
  end
@@ -31,14 +31,25 @@ module DeadEnd
31
31
  class CodeLine
32
32
  TRAILING_SLASH = ("\\" + $/).freeze
33
33
 
34
+ def self.parse(source)
35
+ source.lines.map.with_index do |line, index|
36
+ CodeLine.new(line: line, index: index)
37
+ end
38
+ end
39
+
34
40
  attr_reader :line, :index, :indent, :original_line
35
41
 
36
42
  def initialize(line: , index:)
37
43
  @original_line = line.freeze
38
44
  @line = @original_line
39
- @empty = line.strip.empty?
45
+ if line.strip.empty?
46
+ @empty = true
47
+ @indent = 0
48
+ else
49
+ @empty = false
50
+ @indent = SpaceCount.indent(line)
51
+ end
40
52
  @index = index
41
- @indent = SpaceCount.indent(line)
42
53
  @status = nil # valid, invalid, unknown
43
54
  @invalid = false
44
55
 
@@ -46,24 +57,29 @@ module DeadEnd
46
57
  end
47
58
 
48
59
  private def lex_detect!
49
- lex = LexAll.new(source: line)
60
+ lex_array = LexAll.new(source: line)
50
61
  kw_count = 0
51
62
  end_count = 0
52
- lex.each do |lex|
63
+ lex_array.each_with_index do |lex, index|
53
64
  next unless lex.type == :on_kw
54
65
 
55
66
  case lex.token
56
- when 'def', 'case', 'for', 'begin', 'class', 'module', 'if', 'unless', 'while', 'until' , 'do'
67
+ when 'if', 'unless', 'while', 'until'
68
+ # Only count if/unless when it's not a "trailing" if/unless
69
+ # https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
70
+ kw_count += 1 if !lex.expr_label?
71
+ when 'def', 'case', 'for', 'begin', 'class', 'module', 'do'
57
72
  kw_count += 1
58
73
  when 'end'
59
74
  end_count += 1
60
75
  end
61
76
  end
62
77
 
78
+ @is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
79
+ return if @is_comment
63
80
  @is_kw = (kw_count - end_count) > 0
64
81
  @is_end = (end_count - kw_count) > 0
65
- @is_comment = lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
66
- @is_trailing_slash = lex.last.token == TRAILING_SLASH
82
+ @is_trailing_slash = lex_array.last.token == TRAILING_SLASH
67
83
  end
68
84
 
69
85
  alias :original :original_line
@@ -72,6 +88,10 @@ module DeadEnd
72
88
  @is_trailing_slash
73
89
  end
74
90
 
91
+ def indent_index
92
+ @indent_index ||= [indent, index]
93
+ end
94
+
75
95
  def <=>(b)
76
96
  self.index <=> b.index
77
97
  end
@@ -92,15 +112,6 @@ module DeadEnd
92
112
  @is_end
93
113
  end
94
114
 
95
- def mark_invalid
96
- @invalid = true
97
- self
98
- end
99
-
100
- def marked_invalid?
101
- @invalid
102
- end
103
-
104
115
  def mark_invisible
105
116
  @line = ""
106
117
  self
@@ -75,7 +75,7 @@ module DeadEnd
75
75
  record(block: block, name: name)
76
76
 
77
77
  if block.valid?
78
- block.lines.each(&:mark_invisible)
78
+ block.mark_invisible
79
79
  frontier << block
80
80
  else
81
81
  frontier << block
@@ -92,7 +92,7 @@ module DeadEnd
92
92
 
93
93
  # Parses the most indented lines into blocks that are marked
94
94
  # and added to the frontier
95
- def add_invalid_blocks
95
+ def visit_new_blocks
96
96
  max_indent = frontier.next_indent_line&.indent
97
97
 
98
98
  while (line = frontier.next_indent_line) && (line.indent == max_indent)
@@ -145,7 +145,7 @@ module DeadEnd
145
145
  if frontier.expand?
146
146
  expand_invalid_block
147
147
  else
148
- add_invalid_blocks
148
+ visit_new_blocks
149
149
  end
150
150
  end
151
151
 
@@ -37,9 +37,10 @@ module DeadEnd
37
37
  raise e
38
38
  end
39
39
 
40
- def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT)
40
+ def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
41
41
  search = nil
42
42
  Timeout.timeout(timeout) do
43
+ record_dir ||= ENV["DEBUG"] ? "tmp" : nil
43
44
  search = CodeSearch.new(source, record_dir: record_dir).call
44
45
  end
45
46
 
@@ -50,10 +51,11 @@ module DeadEnd
50
51
  terminal: terminal,
51
52
  code_lines: search.code_lines,
52
53
  invalid_obj: invalid_type(source),
53
- io: $stderr
54
+ io: io
54
55
  ).call
55
- rescue Timeout::Error
56
- $stderr.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
56
+ rescue Timeout::Error => e
57
+ io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
58
+ io.puts e.backtrace.first(3).join($/)
57
59
  end
58
60
 
59
61
  # Used for counting spaces
@@ -21,7 +21,7 @@ module DeadEnd
21
21
  lineno = @lex.last&.first&.first + 1
22
22
  end
23
23
 
24
- @lex.map! {|(line, _), type, token| LexValue.new(line, _, type, token) }
24
+ @lex.map! {|(line, _), type, token, state| LexValue.new(line, _, type, token, state) }
25
25
  end
26
26
 
27
27
  def each
@@ -47,11 +47,17 @@ module DeadEnd
47
47
  # lex.type # => :on_indent
48
48
  # lex.token # => "describe"
49
49
  class LexValue
50
- attr_reader :line, :type, :token
51
- def initialize(line, _, type, token)
50
+ attr_reader :line, :type, :token, :state
51
+
52
+ def initialize(line, _, type, token, state)
52
53
  @line = line
53
54
  @type = type
54
55
  @token = token
56
+ @state = state
57
+ end
58
+
59
+ def expr_label?
60
+ state.allbits?(Ripper::EXPR_LABEL)
55
61
  end
56
62
  end
57
63
  end
@@ -40,7 +40,7 @@ module DeadEnd
40
40
  .skip(:hidden?)
41
41
  .scan_while {|line| line.indent >= target_line.indent }
42
42
 
43
- neighbors = @code_lines[scan.before_index..scan.after_index]
43
+ neighbors = scan.code_block.lines
44
44
 
45
45
  until neighbors.empty?
46
46
  lines = [neighbors.pop]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dead_end
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-10 00:00:00.000000000 Z
11
+ date: 2021-01-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: When you get an "unexpected end" in your syntax this gem helps you find
14
14
  it