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 +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +2 -2
- data/README.md +21 -6
- data/lib/dead_end/around_block_scan.rb +14 -5
- data/lib/dead_end/auto.rb +58 -7
- data/lib/dead_end/capture_code_context.rb +68 -14
- data/lib/dead_end/code_block.rb +4 -1
- data/lib/dead_end/code_frontier.rb +17 -30
- data/lib/dead_end/code_line.rb +27 -16
- data/lib/dead_end/code_search.rb +3 -3
- data/lib/dead_end/internals.rb +6 -4
- data/lib/dead_end/lex_all.rb +9 -3
- data/lib/dead_end/parse_blocks_from_indent_line.rb +1 -1
- data/lib/dead_end/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83ee512040b172db6ab10b5bc6220e856734653bb4c033291a5b2d6210485996
|
4
|
+
data.tar.gz: b0812e0b2f605101c0bdef7f5d08886d404e5c02f4e28bb11e982e6cabef4636
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69cd9208b68cd671f90fe82ef1cf69e2f7037286360665b648690d51e0e9206f4e53abe3c4da755c2fcec08ed76261ca85a7e17c2dfcef7589880d149c33daf3
|
7
|
+
data.tar.gz: b78698db009e17b234da6aa1cbc185b9c5d391e9466685e823911ed2f725b02c0e2d2d14a79fed02a3e907cbd84d96314fb335ea80aa6cc78adf2d1ba8fff4b8
|
data/CHANGELOG.md
CHANGED
@@ -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)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# DeadEnd
|
2
2
|
|
3
|
-
An
|
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
|
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
|
-
|
158
|
-
|
165
|
+
before_after_indent = []
|
166
|
+
before_after_indent << (next_up&.indent || 0)
|
167
|
+
before_after_indent << (next_down&.indent || 0)
|
159
168
|
|
160
|
-
indent =
|
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[
|
199
|
+
@code_lines[after_index.next..-1] || []
|
191
200
|
end
|
192
201
|
end
|
193
202
|
end
|
data/lib/dead_end/auto.rb
CHANGED
@@ -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 :
|
7
|
-
alias_method :
|
8
|
-
alias_method :
|
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
|
-
|
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
|
-
|
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
|
-
|
26
|
+
dead_end_original_require file
|
25
27
|
else
|
26
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
data/lib/dead_end/code_block.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
22
|
-
# is added back to the frontier, then the lines
|
23
|
-
#
|
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
|
-
@
|
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
|
-
|
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 @
|
78
|
+
return true if @unvisited_lines.empty?
|
90
79
|
|
91
80
|
frontier_indent = @frontier.last.current_indent
|
92
|
-
|
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 " @
|
88
|
+
puts " @unvisited indent: #{unvisited_indent}"
|
100
89
|
end
|
101
90
|
|
102
|
-
|
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
|
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
|
data/lib/dead_end/code_line.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
60
|
+
lex_array = LexAll.new(source: line)
|
50
61
|
kw_count = 0
|
51
62
|
end_count = 0
|
52
|
-
|
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 '
|
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
|
-
@
|
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
|
data/lib/dead_end/code_search.rb
CHANGED
@@ -75,7 +75,7 @@ module DeadEnd
|
|
75
75
|
record(block: block, name: name)
|
76
76
|
|
77
77
|
if block.valid?
|
78
|
-
block.
|
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
|
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
|
-
|
148
|
+
visit_new_blocks
|
149
149
|
end
|
150
150
|
end
|
151
151
|
|
data/lib/dead_end/internals.rb
CHANGED
@@ -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:
|
54
|
+
io: io
|
54
55
|
).call
|
55
|
-
rescue Timeout::Error
|
56
|
-
|
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
|
data/lib/dead_end/lex_all.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/dead_end/version.rb
CHANGED
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.
|
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:
|
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
|