dead_end 1.0.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|