dead_end 1.0.2 → 1.1.4
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 +21 -0
- data/Gemfile.lock +2 -2
- data/README.md +21 -6
- data/dead_end.gemspec +1 -1
- data/lib/dead_end/auto.rb +60 -7
- 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 +3 -4
- data/assets/syntax_search.gif +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1e8a394d94d7d8f70138a9d80d7e660cca8dc06e3d3a5eb9b74334585f284aa
|
4
|
+
data.tar.gz: 75296d7faaf4b117ad45bed40252566ee08752b622b2d90e4afa3be8b4a9b774
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f970086da7e3f662d2dabda9a59d89af73d503dc986a9d469d45b83f25184590892e5bdb0a4c174318b2d1bb201f112c81a0a281d0a530feb49dd7f0eb1fb144
|
7
|
+
data.tar.gz: cc85e81a1d996320128d7656c0cea0727f9672060d7d2747e30e77d2aeb59d3549f4dd230835c5042988c53b1252f975c472774eb2ef7f258caebea5cd5eee40
|
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,26 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 1.1.4
|
4
|
+
|
5
|
+
- Avoid including demo gif in built gem (https://github.com/zombocom/dead_end/pull/53)
|
6
|
+
|
7
|
+
## 1.1.3
|
8
|
+
|
9
|
+
- Add compatibility with zeitwerk (https://github.com/zombocom/dead_end/pull/52)
|
10
|
+
|
11
|
+
## 1.1.2
|
12
|
+
|
13
|
+
- Namespace Kernel method aliases (https://github.com/zombocom/dead_end/pull/51)
|
14
|
+
|
15
|
+
## 1.1.1
|
16
|
+
|
17
|
+
- Safer NoMethodError annotation (https://github.com/zombocom/dead_end/pull/48)
|
18
|
+
|
19
|
+
## 1.1.0
|
20
|
+
|
21
|
+
- Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
|
22
|
+
- Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
|
23
|
+
|
3
24
|
## 1.0.2
|
4
25
|
|
5
26
|
- 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/dead_end.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
22
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
|
24
24
|
end
|
25
25
|
spec.bindir = "exe"
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
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
|
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.
|
4
|
+
version: 1.1.4
|
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-26 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
|
@@ -31,7 +31,6 @@ files:
|
|
31
31
|
- LICENSE.txt
|
32
32
|
- README.md
|
33
33
|
- Rakefile
|
34
|
-
- assets/syntax_search.gif
|
35
34
|
- bin/console
|
36
35
|
- bin/setup
|
37
36
|
- dead_end.gemspec
|
@@ -76,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
75
|
- !ruby/object:Gem::Version
|
77
76
|
version: '0'
|
78
77
|
requirements: []
|
79
|
-
rubygems_version: 3.
|
78
|
+
rubygems_version: 3.0.3
|
80
79
|
signing_key:
|
81
80
|
specification_version: 4
|
82
81
|
summary: Find syntax errors in your source in a snap
|
data/assets/syntax_search.gif
DELETED
Binary file
|