dead_end 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/README.md +21 -6
- data/lib/dead_end/auto.rb +48 -0
- data/lib/dead_end/code_line.rb +15 -5
- data/lib/dead_end/lex_all.rb +9 -3
- 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: 8b372fd8bb6cb9f7b3a601fcb32a539ae361fa19e3f64471d6c446564532750c
|
4
|
+
data.tar.gz: fc78d6a4ff9f86663af64dc6acf1bf2a0bced8fded065a2e0b52210e19766496
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ee131a4f8574bec7a8e9054b4085524cf49692afeb40387144f9461f34323b533944b1c9683b0772c43858ed348fb6eb889ae89ecfb765b0c26b805e93dee4c
|
7
|
+
data.tar.gz: bffe41e67a11e342ad5db8a36b7faa81c1ea24eab2a37bb67c77762cd0be470f12cc55b8baf9e9696e2ed321943eed348328b637220102d2e9e1678c17ef3d18
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 1.1.0
|
4
|
+
|
5
|
+
- Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
|
6
|
+
- Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
|
7
|
+
|
3
8
|
## 1.0.2
|
4
9
|
|
5
10
|
- Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
|
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
|
|
data/lib/dead_end/auto.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
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
|
@@ -49,3 +51,49 @@ 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
|
+
def to_s
|
66
|
+
return super if DeadEnd::IsProduction.call
|
67
|
+
|
68
|
+
file, line, _ = backtrace[0].split(":")
|
69
|
+
return super if !File.exist?(file)
|
70
|
+
|
71
|
+
index = line.to_i - 1
|
72
|
+
source = File.read(file)
|
73
|
+
code_lines = DeadEnd::CodeLine.parse(source)
|
74
|
+
|
75
|
+
block = DeadEnd::CodeBlock.new(lines: code_lines[index])
|
76
|
+
lines = DeadEnd::CaptureCodeContext.new(
|
77
|
+
blocks: block,
|
78
|
+
code_lines: code_lines
|
79
|
+
).call
|
80
|
+
|
81
|
+
message = super.dup
|
82
|
+
message << $/
|
83
|
+
message << $/
|
84
|
+
|
85
|
+
message << DeadEnd::DisplayCodeWithLineNumbers.new(
|
86
|
+
lines: lines,
|
87
|
+
highlight_lines: block.lines,
|
88
|
+
terminal: self.class.to_tty?
|
89
|
+
).call
|
90
|
+
|
91
|
+
message << $/
|
92
|
+
message
|
93
|
+
rescue => e
|
94
|
+
puts "DeadEnd Internal error: #{e.message}"
|
95
|
+
puts "DeadEnd Internal backtrace: #{e.backtrace}"
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/dead_end/code_line.rb
CHANGED
@@ -31,6 +31,12 @@ 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:)
|
@@ -51,25 +57,29 @@ module DeadEnd
|
|
51
57
|
end
|
52
58
|
|
53
59
|
private def lex_detect!
|
54
|
-
|
60
|
+
lex_array = LexAll.new(source: line)
|
55
61
|
kw_count = 0
|
56
62
|
end_count = 0
|
57
|
-
|
63
|
+
lex_array.each_with_index do |lex, index|
|
58
64
|
next unless lex.type == :on_kw
|
59
65
|
|
60
66
|
case lex.token
|
61
|
-
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'
|
62
72
|
kw_count += 1
|
63
73
|
when 'end'
|
64
74
|
end_count += 1
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
68
|
-
@is_comment =
|
78
|
+
@is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
|
69
79
|
return if @is_comment
|
70
80
|
@is_kw = (kw_count - end_count) > 0
|
71
81
|
@is_end = (end_count - kw_count) > 0
|
72
|
-
@is_trailing_slash =
|
82
|
+
@is_trailing_slash = lex_array.last.token == TRAILING_SLASH
|
73
83
|
end
|
74
84
|
|
75
85
|
alias :original :original_line
|
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.0
|
4
|
+
version: 1.1.0
|
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-
|
11
|
+
date: 2020-12-29 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
|