dead_end 1.0.1 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +9 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +2 -2
- data/README.md +21 -6
- data/lib/dead_end/around_block_scan.rb +2 -2
- data/lib/dead_end/auto.rb +60 -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 +16 -29
- data/lib/dead_end/code_line.rb +27 -16
- data/lib/dead_end/code_search.rb +3 -3
- data/lib/dead_end/lex_all.rb +9 -3
- data/lib/dead_end/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00641720122b144830e8eccf13485745712d861158a7b928969cfcbadb52d53e
|
4
|
+
data.tar.gz: b9f874797083cca9ac2925023890553f1af3cbafbd52e6719a073de9b236bad7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9cfe06766beb27cc43a7197a665e9245c2b1de0343679699c98f047afb207ad64a52855102e9fda5071c7ad7ca767fc2b29f986eb6f030250e9613c0615d879
|
7
|
+
data.tar.gz: c19544c98f18954a5166c88bcc2987ee9e30db2344753f8aa1cc85645e4328bcc2442fd0b495c5c4bbd19af094760cfa180a1c4f017f395941cd844c3274ad70
|
data/.circleci/config.yml
CHANGED
@@ -32,6 +32,14 @@ jobs:
|
|
32
32
|
- ruby/install-deps
|
33
33
|
- <<: *unit
|
34
34
|
|
35
|
+
"ruby-3-0":
|
36
|
+
docker:
|
37
|
+
- image: circleci/ruby:3.0
|
38
|
+
steps:
|
39
|
+
- checkout
|
40
|
+
- ruby/install-deps
|
41
|
+
- <<: *unit
|
42
|
+
|
35
43
|
workflows:
|
36
44
|
version: 2
|
37
45
|
build:
|
@@ -39,3 +47,4 @@ workflows:
|
|
39
47
|
- "ruby-2-5"
|
40
48
|
- "ruby-2-6"
|
41
49
|
- "ruby-2-7"
|
50
|
+
- "ruby-3-0"
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 1.1.3
|
4
|
+
|
5
|
+
- Add compatibility with zeitwerk (https://github.com/zombocom/dead_end/pull/52)
|
6
|
+
|
7
|
+
## 1.1.2
|
8
|
+
|
9
|
+
- Namespace Kernel method aliases (https://github.com/zombocom/dead_end/pull/51)
|
10
|
+
|
11
|
+
## 1.1.1
|
12
|
+
|
13
|
+
- Safer NoMethodError annotation (https://github.com/zombocom/dead_end/pull/48)
|
14
|
+
|
15
|
+
## 1.1.0
|
16
|
+
|
17
|
+
- Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
|
18
|
+
- Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
|
19
|
+
|
20
|
+
## 1.0.2
|
21
|
+
|
22
|
+
- Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
|
23
|
+
- 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)
|
24
|
+
|
3
25
|
## 1.0.1
|
4
26
|
|
5
27
|
- Fix performance issue when evaluating multiple block combinations (https://github.com/zombocom/dead_end/pull/35)
|
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
|
|
@@ -163,8 +163,8 @@ module DeadEnd
|
|
163
163
|
|
164
164
|
def scan_adjacent_indent
|
165
165
|
before_after_indent = []
|
166
|
-
before_after_indent << next_up&.indent || 0
|
167
|
-
before_after_indent << next_down&.indent || 0
|
166
|
+
before_after_indent << (next_up&.indent || 0)
|
167
|
+
before_after_indent << (next_down&.indent || 0)
|
168
168
|
|
169
169
|
indent = before_after_indent.min
|
170
170
|
self.scan_while {|line| line.not_empty? && line.indent >= indent }
|
data/lib/dead_end/auto.rb
CHANGED
@@ -1,29 +1,33 @@
|
|
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
|
-
|
7
|
-
|
8
|
-
alias_method :
|
8
|
+
module_function
|
9
|
+
|
10
|
+
alias_method :dead_end_original_require, :require
|
11
|
+
alias_method :dead_end_original_require_relative, :require_relative
|
12
|
+
alias_method :dead_end_original_load, :load
|
9
13
|
|
10
14
|
def load(file, wrap = false)
|
11
|
-
|
15
|
+
dead_end_original_load(file)
|
12
16
|
rescue SyntaxError => e
|
13
17
|
DeadEnd.handle_error(e)
|
14
18
|
end
|
15
19
|
|
16
20
|
def require(file)
|
17
|
-
|
21
|
+
dead_end_original_require(file)
|
18
22
|
rescue SyntaxError => e
|
19
23
|
DeadEnd.handle_error(e)
|
20
24
|
end
|
21
25
|
|
22
26
|
def require_relative(file)
|
23
27
|
if Pathname.new(file).absolute?
|
24
|
-
|
28
|
+
dead_end_original_require file
|
25
29
|
else
|
26
|
-
|
30
|
+
dead_end_original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
|
27
31
|
end
|
28
32
|
rescue SyntaxError => e
|
29
33
|
DeadEnd.handle_error(e)
|
@@ -49,3 +53,52 @@ class Object
|
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
56
|
+
module DeadEnd
|
57
|
+
IsProduction = -> {
|
58
|
+
ENV["RAILS_ENV"] == "production" || ENV["RACK_ENV"] == "production"
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unlike a syntax error, a NoMethodError can occur hundreds or thousands of times and
|
63
|
+
# chew up CPU and other resources. Since this is primarilly a "development" optimization
|
64
|
+
# we can attempt to disable this behavior in a production context.
|
65
|
+
if !DeadEnd::IsProduction.call
|
66
|
+
class NoMethodError
|
67
|
+
alias :dead_end_original_to_s :to_s
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
return super if DeadEnd::IsProduction.call
|
71
|
+
|
72
|
+
file, line, _ = backtrace[0].split(":")
|
73
|
+
return super if !File.exist?(file)
|
74
|
+
|
75
|
+
index = line.to_i - 1
|
76
|
+
source = File.read(file)
|
77
|
+
code_lines = DeadEnd::CodeLine.parse(source)
|
78
|
+
|
79
|
+
block = DeadEnd::CodeBlock.new(lines: code_lines[index])
|
80
|
+
lines = DeadEnd::CaptureCodeContext.new(
|
81
|
+
blocks: block,
|
82
|
+
code_lines: code_lines
|
83
|
+
).call
|
84
|
+
|
85
|
+
message = super.dup
|
86
|
+
message << $/
|
87
|
+
message << $/
|
88
|
+
|
89
|
+
message << DeadEnd::DisplayCodeWithLineNumbers.new(
|
90
|
+
lines: lines,
|
91
|
+
highlight_lines: block.lines,
|
92
|
+
terminal: self.class.to_tty?
|
93
|
+
).call
|
94
|
+
|
95
|
+
message << $/
|
96
|
+
message
|
97
|
+
rescue => e
|
98
|
+
puts "DeadEnd Internal error: #{e.dead_end_original_to_s}"
|
99
|
+
puts "DeadEnd Internal backtrace:"
|
100
|
+
puts backtrace.map {|l| " " + l }.join($/)
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
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
|
|
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/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.3
|
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-21 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
|
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
78
|
requirements: []
|
79
|
-
rubygems_version: 3.
|
79
|
+
rubygems_version: 3.2.3
|
80
80
|
signing_key:
|
81
81
|
specification_version: 4
|
82
82
|
summary: Find syntax errors in your source in a snap
|